Setup

rm(list = ls(all.names = TRUE))

if(!require("pacman")) install.packages("pacman"); library(pacman)

pacman::p_load(
  tidyverse,
  scales,
  janitor,
  GCLr,
  coin
)

knitr::opts_chunk$set(fig.width = 10)
knitr::opts_chunk$set(echo = TRUE)

.username = readLines("~/usr_pw.txt", n = 1)  # LOKI username
.password = readLines("~/usr_pw.txt" , n = 2)[[2]]  # LOKI password

Objective

The purpose of this notebook is to provide Wes Larson (NOAA ABL) with adaptive run timing genotypes (9 loci) for AHRP chum samples. Filter out plates undergoing QC crosschecks due to poor genotyping success. Wes is interested in representation across years, less across multiple streams. He wants to see how the patterns hold up over time.

Steps

  • import all existing genotypes for CM064-CM070
  • filter out untrustworthy plates that need QC cross checks
  • standard GT-seq genotype QA (missing, heterozygosity, duplicate)
  • join sample date and spawning state <“../OceanAK/AHRP Salmon Biological Data 20240105_122404.368459_with_spawning_state.csv”>
  • filter for 9 adaptive run timing loci
  • export genotypes with date/spawning state

Background

An adaptive marker panel associated with run timing variation in chum salmon was developed by NOAA ABL and used to genotype two plates of samples from the ADFG Alaska Hatchery Research Program (AHRP) study being conducted within SE Alaska. AHRP samples (Fish Creek - Douglas Island 2023 + Prospect Creek 2017/2018) were genotyped in August 2024 for 20 single nucleotide polymorphisms and weighted linear regressions were performed to assess the association of different SNPs with run timing (date of collection). The chum salmon adaptive marker panel contains 20 loci spread among 5 chromosomes and was developed from variation detected between Yukon River summer (Gisasa River) and fall (Pelly River) run chum salmon with low coverage whole genome sequencing.These populations spawn approximately two months and over 1500 river km apart. NOAA targeted the major peaks in differentiation with particular interest in markers within the genes leucine rich repeat containing nine (LRRC9; LG35) and estrogen receptor β (ESRB; LG29) as they were associated with return timing in other salmonid species. Eight SNPs (four on LG29 and four on LG35) appear to be associated with run timing within these collections. The strength of association for AHRP samples appears to vary by population. No markers on LGs 5, 23 or 15 appear to have a strong association with run timing within these collections. Overall, the results from LG29 and LG35 are promising and variation in these SNPs should be surveyed for a larger collection of samples.

A total of 9 adaptive, run timing loci were added to the AHRP SEAK Chum parentage panel:

  • OkeV2_LG23_10546237
  • OkeV2_LG29_25450752
  • OkeV2_LG29_25455833
  • OkeV2_LG29_25483595
  • OkeV2_LG29_25515161
  • OkeV2_LG35_28078867
  • OkeV2_LG35_28128687
  • OkeV2_LG35_28165076
  • OkeV2_LG35_28167172

All AHRP Chum samples from Fish Creek - Douglas Island, Prospect Creek, Admiralty Creek, and Sawmill Creek were genotyped for the parentage panel at the ADF&G Gene Conservation Laboratory (GCL) in Winter/Spring 2025. Due to poor tissue quality, there was a high proportion of samples that failed to genotype and/or appear to be contaminated (high heterozygosity), resulting in low QC power for several plates. QC crosschecks are currently underway at the GCL in July 2025, however, Wes wants whatever genotypes we have on hand now so will just filter out those plates.

Import Data

LocusControl

Create objects, create LocusControl for Chum_AHRP_257_v1.3.0.

fs::dir_create("objects")
GCLr::create_locuscontrol(markersuite = "Chum_AHRP_257_v1.3.0",
                          username = .username,
                          password = .password)
GCLr::save_objects(objects = "LocusControl", path = "objects")

Hrmm, only 251 loci. Confirm that these are the same loci run in the most recent AHRP Chum project CM069

probe_loci_CM069 <- readr::read_tsv(
  file = "V:/Lab/Genotyping/SNP Projects/Chum/Project CM069 AHRP Parentage/Genotype Data Files/set2rr_CM069_outputs/probes.txt", 
  show_col_types = FALSE) %>%
  dplyr::distinct(`#Locus_ID`) %>%
  dplyr::pull()

all.equal(probe_loci_CM069, LocusControl$locusnames)

Yes, all are the same.

loci251 <- LocusControl$locusnames
GCLr::save_objects(objects = "loci251", path = "objects")

Genotypes

Read in genotypes by projec to get sillyvec.

rm(LocusControl)
# GCLr::loki2r_proj(project_name = paste0("CM0", 64:70), username = .username, password = .password)  # comment out to avoid re-running

What collections?

(sillyvec <- sort(project_sillys))

Subset sillyvec to remove alevin collection

(sillyvec <- grep(pattern = "a", x = sillyvec, ignore.case = FALSE, value = TRUE, invert = TRUE))

Wipe out .gcl objects and re-read in using loki2r.

GCLr::save_objects(objects = "sillyvec", path = "objects")

rm(list = objects(pattern = ".gcl"))
rm(LocusControl)

Read in genotypes.

GCLr::load_objects(path = "objects", pattern = "LocusControl")

GCLr::loki2r(sillyvec = sillyvec,
             username = .username,
             password = .password, 
             test_type = "GTSNP")

Only took 5.39 minutes to read everything in at the office!

Save raw genotypes (useful for working offsite and off VPN).

fs::dir_create("data/genotypes")
fs::dir_create("data/genotypes/raw")
# GCLr::save_sillys(sillyvec = sillyvec, path = "data/genotypes/raw", rds = TRUE)  # hide so you don't overwrite on accident!

Re-load

Re-load, if necessary

GCLr::load_objects(path = "objects")
[1] "loci251"      "LocusControl" "sillyvec"    
GCLr::load_sillys(sillyvec = sillyvec, path = "data/genotypes/raw", rds = TRUE)
 [1] "CMADMCR13.gcl"   "CMADMCR14.gcl"   "CMADMCR17.gcl"   "CMADMCR18.gcl"   "CMFISHCR13.gcl"  "CMFISHCR14.gcl"  "CMFISHCR17.gcl"  "CMFISHCR18.gcl" 
 [9] "CMFISHCR19.gcl"  "CMFISHCR20.gcl"  "CMFISHCR21.gcl"  "CMFISHCR22.gcl"  "CMFISHCR23.gcl"  "CMFISHCRT13.gcl" "CMFISHCRT21.gcl" "CMFISHCRT22.gcl"
[17] "CMFISHCRT23.gcl" "CMPROSCR13.gcl"  "CMPROSCR14.gcl"  "CMPROSCR17.gcl"  "CMPROSCR18.gcl"  "CMPROSCR19.gcl"  "CMPROSCR20.gcl"  "CMPROSCR21.gcl" 
[25] "CMPROSCR22.gcl"  "CMPROSCRT21.gcl" "CMPROSCRT22.gcl" "CMSAWCR13.gcl"   "CMSAWCR14.gcl"   "CMSAWCR15.gcl"   "CMSAWCR17.gcl"   "CMSAWCR18.gcl"  
[33] "CMSAWCR19.gcl"   "CMSAWCR20.gcl"   "CMSAWCR21.gcl"   "CMSAWCR22.gcl"   "CMSAWCRT21.gcl"  "CMSAWCRT22.gcl" 

Metadata

Read in paired metadata from Salmon Biological Fact and Stream Specimens, format similar to pws_pink_ped_dat. NOTE I’m doing this in a bit of a hurry, so make sure to proof read carefully if recycling this code!

(metadata <- readr::read_csv(file = "../OceanAK/AHRP Salmon Biological Data 20240105_122404.368459_with_spawning_state.csv"))
Rows: 27046 Columns: 25
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (10): silly_code, spawning_state, sex, tissue_type, dna_tray_code, location_code, sample_id, otolith_mark_present, otolith_mark_id, otolith_...
dbl   (9): collection_id, fish_id, length_mm, dna_tray_well_code, sample_year, is_missing_paired_data_exists, well_has_more_than_one_sample, fw_a...
lgl   (5): target_dna_tray_code, target_dna_tray_well_pos, target_container_array_type_id, container_array_type, determination_collection_id
date  (1): sample_date

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Modify

(
  metadata <- metadata %>% 
    dplyr::select(-dplyr::contains("target")) %>% 
    dplyr::select(-dplyr::contains("determination")) %>% 
    dplyr::rename(date = sample_date,
                  length = length_mm,
                  silly = silly_code) %>% 
    tidyr::unite(col = "sample", dna_tray_code:dna_tray_well_code, sep = "_", remove = FALSE) %>% 
    tidyr::unite(col = "silly_source", c(silly, fish_id), sep = "_", remove = FALSE) %>% 
    dplyr::mutate(franz_id = stringr::str_c(stringr::str_sub(silly, 1, 2),  # 1st letter of species name, 1st letter of stream name
                                            stringr::str_sub(silly, -2, -1),  # 2 digit sample year
                                            "_",
                                            stringr::str_pad(fish_id, width = 5, pad = "0", side = "left")),  # 5 digit fishid using padded zeros up front)
                  year = lubridate::year(date),
                  stream = stringr::str_remove(string = location_code, pattern = " Creek"),
                  DOY = lubridate::yday(date),  # day of year
                  sex = dplyr::case_when(
                    sex == "M" ~ "Male",
                    sex == "F" ~ "Female",
                    sex == "U" ~ NA_character_,
                    is.na(sex) ~ NA_character_),
                  origin = dplyr::case_when(
                    otolith_mark_present == "NO" ~ "Natural",
                    otolith_mark_present == "YES" ~ "Hatchery"
                  ),  # add origin variable
                  origin = base::factor(origin, levels = c("Natural", "Hatchery")),  # make factor to ensure hatchery != red
                  spawning_state = dplyr::case_when(
                    spawning_state == "Unknown" ~ NA_character_,
                    TRUE ~ spawning_state
                  )  # force Unknown to NA
    ) %>%  
    dplyr::select(
      franz_id,  # unique identifier for FRANz (e.g. PE16_00001, species, stream, year, GCL fish_id)
      sample,  # ultimate data key for AHRP, DWP barcode + DWP position
      silly_source,  # unique identifier for LOKI (GCL database)
      stream,  # 5 pedigree streams
      year,  # sample year (aka death year for FRANz)
      origin,  # hatchery or wild
      # hatchery,  # which hatchery
      sex,  # male, female, unknown
      date,  # sample date
      DOY,  # day of year (aka julian sample date)
      length,  # mideye to hypural plate length in mm
      # intertidal,  # sampling location above or below intertidal
      # distance_mouth,  # riverdist approximate sampling location from mouth in meters 
      spawning_state,  # alive, pink gill, grey gill, rotting (aka index of stream life)
      # pre_spawn,  # TRUE/FALSE (added in 2016); if the eggs and milt do not easily release and carcass has completely full gonads
      # partial_spawn,  # TRUE/FALSE (added in 2015); fish is sampled (dead or alive) with more than a little eggs or milt
      # preyed_upon,  # TRUE/FALSE (added in 2015); gonads removed by predators
      # stream_trib,  # stream tributary (Gilmour, Paddy, and Stockdale have multiple)
      # latitude,  # sampling area (where fish were moved to prior to sampling, rough approximate of spawning location)
      # longitude,  # sampling area (where fish were moved to prior to sampling, rough approximate of spawning location)
      otolith_mark_present,  # otolith mark present (yes = hatchery, no = natural)
      otolith_mark_id,  # otolith hatchery mark
      otolith_mark_status_code,  # otolith read status code (reason for no read = overground, lost, etc.)
      silly,  # GCL collection code
      fish_id,  # GCL fish ID within a silly
      dna_tray_code,  # 10-digit DWP barcode
      dna_tray_well_code,  # 48 DWP position 1:48
      # dna_tray_well_pos,  # 48 DWP position A1:H6
      # distance_tide,  # riverdist approximate sampling location from maximum high tide
      # riverdist_seg,  # riverdist river segment
      # riverdist_vert,  # riverdist vertex
      # riverdist_snapdist,  # riverdist snapping distance (aka how far was sampling location lat/long from the stream polyline)
      # high_tide  # highest extent of high tide in meters from mouth, unique to each stream
      fw_age,
      sw_age
    )  
)       

Sample Sizes

Verify that all genotyped samples have metadata.

geno_silly_source <- sapply(sillyvec, function(silly) {get(paste0(silly, ".gcl"))$SillySource}) %>% unlist()

table(geno_silly_source %in% metadata$silly_source)

FALSE  TRUE 
  241 18569 

Shoot, which samples do not have any metadata?

Ah, I bet they are the Fish Creek tag samples from 2013 that Tyler Dann and Heather Liller collected!

setdiff(geno_silly_source, metadata$silly_source)
  [1] "CMFISHCRT13_1"   "CMFISHCRT13_2"   "CMFISHCRT13_3"   "CMFISHCRT13_4"   "CMFISHCRT13_5"   "CMFISHCRT13_6"   "CMFISHCRT13_7"   "CMFISHCRT13_8"  
  [9] "CMFISHCRT13_9"   "CMFISHCRT13_10"  "CMFISHCRT13_11"  "CMFISHCRT13_12"  "CMFISHCRT13_13"  "CMFISHCRT13_14"  "CMFISHCRT13_15"  "CMFISHCRT13_16" 
 [17] "CMFISHCRT13_17"  "CMFISHCRT13_18"  "CMFISHCRT13_19"  "CMFISHCRT13_20"  "CMFISHCRT13_21"  "CMFISHCRT13_22"  "CMFISHCRT13_23"  "CMFISHCRT13_24" 
 [25] "CMFISHCRT13_25"  "CMFISHCRT13_26"  "CMFISHCRT13_27"  "CMFISHCRT13_28"  "CMFISHCRT13_29"  "CMFISHCRT13_30"  "CMFISHCRT13_31"  "CMFISHCRT13_32" 
 [33] "CMFISHCRT13_33"  "CMFISHCRT13_34"  "CMFISHCRT13_35"  "CMFISHCRT13_36"  "CMFISHCRT13_37"  "CMFISHCRT13_38"  "CMFISHCRT13_39"  "CMFISHCRT13_40" 
 [41] "CMFISHCRT13_41"  "CMFISHCRT13_42"  "CMFISHCRT13_43"  "CMFISHCRT13_44"  "CMFISHCRT13_45"  "CMFISHCRT13_46"  "CMFISHCRT13_47"  "CMFISHCRT13_48" 
 [49] "CMFISHCRT13_49"  "CMFISHCRT13_50"  "CMFISHCRT13_51"  "CMFISHCRT13_52"  "CMFISHCRT13_53"  "CMFISHCRT13_54"  "CMFISHCRT13_55"  "CMFISHCRT13_56" 
 [57] "CMFISHCRT13_57"  "CMFISHCRT13_58"  "CMFISHCRT13_59"  "CMFISHCRT13_60"  "CMFISHCRT13_61"  "CMFISHCRT13_62"  "CMFISHCRT13_63"  "CMFISHCRT13_64" 
 [65] "CMFISHCRT13_65"  "CMFISHCRT13_66"  "CMFISHCRT13_67"  "CMFISHCRT13_68"  "CMFISHCRT13_69"  "CMFISHCRT13_70"  "CMFISHCRT13_71"  "CMFISHCRT13_72" 
 [73] "CMFISHCRT13_73"  "CMFISHCRT13_74"  "CMFISHCRT13_75"  "CMFISHCRT13_76"  "CMFISHCRT13_77"  "CMFISHCRT13_78"  "CMFISHCRT13_79"  "CMFISHCRT13_80" 
 [81] "CMFISHCRT13_81"  "CMFISHCRT13_82"  "CMFISHCRT13_83"  "CMFISHCRT13_84"  "CMFISHCRT13_85"  "CMFISHCRT13_86"  "CMFISHCRT13_87"  "CMFISHCRT13_88" 
 [89] "CMFISHCRT13_89"  "CMFISHCRT13_90"  "CMFISHCRT13_91"  "CMFISHCRT13_92"  "CMFISHCRT13_93"  "CMFISHCRT13_94"  "CMFISHCRT13_95"  "CMFISHCRT13_96" 
 [97] "CMFISHCRT13_97"  "CMFISHCRT13_98"  "CMFISHCRT13_99"  "CMFISHCRT13_100" "CMFISHCRT13_101" "CMFISHCRT13_102" "CMFISHCRT13_103" "CMFISHCRT13_104"
[105] "CMFISHCRT13_105" "CMFISHCRT13_106" "CMFISHCRT13_107" "CMFISHCRT13_108" "CMFISHCRT13_109" "CMFISHCRT13_110" "CMFISHCRT13_111" "CMFISHCRT13_112"
[113] "CMFISHCRT13_113" "CMFISHCRT13_114" "CMFISHCRT13_115" "CMFISHCRT13_116" "CMFISHCRT13_117" "CMFISHCRT13_118" "CMFISHCRT13_119" "CMFISHCRT13_120"
[121] "CMFISHCRT13_121" "CMFISHCRT13_122" "CMFISHCRT13_123" "CMFISHCRT13_124" "CMFISHCRT13_125" "CMFISHCRT13_126" "CMFISHCRT13_127" "CMFISHCRT13_128"
[129] "CMFISHCRT13_129" "CMFISHCRT13_130" "CMFISHCRT13_131" "CMFISHCRT13_132" "CMFISHCRT13_133" "CMFISHCRT13_134" "CMFISHCRT13_135" "CMFISHCRT13_136"
[137] "CMFISHCRT13_137" "CMFISHCRT13_138" "CMFISHCRT13_139" "CMFISHCRT13_140" "CMFISHCRT13_141" "CMFISHCRT13_142" "CMFISHCRT13_143" "CMFISHCRT13_144"
[145] "CMFISHCRT13_145" "CMFISHCRT13_146" "CMFISHCRT13_147" "CMFISHCRT13_148" "CMFISHCRT13_149" "CMFISHCRT13_150" "CMFISHCRT13_151" "CMFISHCRT13_152"
[153] "CMFISHCRT13_153" "CMFISHCRT13_154" "CMFISHCRT13_155" "CMFISHCRT13_156" "CMFISHCRT13_157" "CMFISHCRT13_158" "CMFISHCRT13_159" "CMFISHCRT13_160"
[161] "CMFISHCRT13_161" "CMFISHCRT13_162" "CMFISHCRT13_163" "CMFISHCRT13_164" "CMFISHCRT13_165" "CMFISHCRT13_166" "CMFISHCRT13_167" "CMFISHCRT13_168"
[169] "CMFISHCRT13_169" "CMFISHCRT13_170" "CMFISHCRT13_171" "CMFISHCRT13_172" "CMFISHCRT13_173" "CMFISHCRT13_174" "CMFISHCRT13_175" "CMFISHCRT13_176"
[177] "CMFISHCRT13_177" "CMFISHCRT13_178" "CMFISHCRT13_179" "CMFISHCRT13_180" "CMFISHCRT13_181" "CMFISHCRT13_182" "CMFISHCRT13_183" "CMFISHCRT13_184"
[185] "CMFISHCRT13_185" "CMFISHCRT13_186" "CMFISHCRT13_187" "CMFISHCRT13_188" "CMFISHCRT13_189" "CMFISHCRT13_190" "CMFISHCRT13_191" "CMFISHCRT13_192"
[193] "CMFISHCRT13_193" "CMFISHCRT13_194" "CMFISHCRT13_195" "CMFISHCRT13_196" "CMFISHCRT13_197" "CMFISHCRT13_198" "CMFISHCRT13_199" "CMFISHCRT13_200"
[201] "CMFISHCRT13_201" "CMFISHCRT13_202" "CMFISHCRT13_203" "CMFISHCRT13_204" "CMFISHCRT13_205" "CMFISHCRT13_206" "CMFISHCRT13_207" "CMFISHCRT13_208"
[209] "CMFISHCRT13_209" "CMFISHCRT13_210" "CMFISHCRT13_211" "CMFISHCRT13_212" "CMFISHCRT13_213" "CMFISHCRT13_214" "CMFISHCRT13_215" "CMFISHCRT13_216"
[217] "CMFISHCRT13_217" "CMFISHCRT13_218" "CMFISHCRT13_219" "CMFISHCRT13_220" "CMFISHCRT13_221" "CMFISHCRT13_222" "CMFISHCRT13_223" "CMFISHCRT13_224"
[225] "CMFISHCRT13_225" "CMFISHCRT13_226" "CMFISHCRT13_227" "CMFISHCRT13_228" "CMFISHCRT13_229" "CMFISHCRT13_230" "CMFISHCRT13_231" "CMFISHCRT13_232"
[233] "CMFISHCRT13_233" "CMFISHCRT13_234" "CMFISHCRT13_235" "CMFISHCRT13_236" "CMFISHCRT13_237" "CMFISHCRT13_238" "CMFISHCRT13_239" "CMFISHCRT13_240"
[241] "CMFISHCRT13_241"

Correct. Add these to metadata.

metadata <- 
  dplyr::bind_rows(metadata,
                   CMFISHCRT13.gcl %>% 
                     dplyr::select(SILLY_CODE, FK_FISH_ID, SillySource, DNA_TRAY_CODE, DNA_TRAY_WELL_CODE, CAPTURE_DATE) %>% 
                     dplyr::rename(silly = SILLY_CODE,
                                   fish_id = FK_FISH_ID,
                                   silly_source = SillySource, 
                                   dna_tray_code = DNA_TRAY_CODE,
                                   dna_tray_well_code = DNA_TRAY_WELL_CODE,
                                   date = CAPTURE_DATE) %>% 
                     tidyr::unite(col = "sample", dna_tray_code:dna_tray_well_code, sep = "_", remove = FALSE) %>% 
                     tidyr::unite(col = "silly_source", c(silly, fish_id), sep = "_", remove = FALSE) %>% 
                     dplyr::mutate(franz_id = stringr::str_c(stringr::str_sub(silly, 1, 2),  # 1st letter of species name, 1st letter of stream name
                                                             stringr::str_sub(silly, -2, -1),  # 2 digit sample year
                                                             "_",
                                                             stringr::str_pad(fish_id, width = 5, pad = "0", side = "left")),  # 5 digit fishid using padded zeros up front)
                                   date = lubridate::as_date(date),
                                   year = lubridate::year(date),
                                   stream = "Fish - Douglas Island",
                                   DOY = lubridate::yday(date)  # day of year
                     ) 
)

Verify all samples have metadata.

table(geno_silly_source %in% metadata$silly_source)

 TRUE 
18810 

Excellent, all present and accounted for.

Record which samples were genotyped.

metadata <- metadata %>% 
  dplyr::mutate(genotyped = silly_source %in% geno_silly_source)

How many samples per collection (silly)?

metadata %>% 
  dplyr::count(genotyped, silly) %>% 
  tidyr::pivot_wider(names_from = genotyped, values_from = n)

Check For Sample Duplicates

I got a weird many-to-many error in a join the first time I ran through this script, so I want to check and see if there are actually duplicate samples present and if so, remove them!

# bind all samples together into a single .gcl object
my.gcl <- sapply(sillyvec, function(silly) {
  my.silly <- get(paste0(silly, ".gcl"))
}, simplify = FALSE) %>% 
  dplyr::bind_rows()

Any duplicate SillySource?

table(table(my.gcl$SillySource))

    1 
18810 

Clean up

Are there metadata duplicates?

table(table(metadata$silly_source))

    1     2 
27197    45 

Well son of a biscuit, there are!

metadata %>% 
  dplyr::filter(duplicated(silly_source) | duplicated(silly_source, fromLast = TRUE)) %>% 
  dplyr::arrange(silly_source)

Okay, they appear to be true duplicates, no conflicting columns. Remove duplicate rows.

metadata <- metadata %>% 
  dplyr::distinct(silly_source, .keep_all = TRUE)

Join Metadata

Join metadata to each silly by SillySource.

x <- sapply(sillyvec, function(silly) {
  my.silly <- get(paste0(silly, ".gcl"))
  my.silly <- my.silly %>% dplyr::left_join(
    y = metadata %>% dplyr::select(-genotyped),
    by = dplyr::join_by(SillySource == silly_source)
  )
  assign(x = paste0(silly, ".gcl"),
         value = my.silly,
         pos = 1)
})
rm(x)  # suppresses output

Filter QC Crosscheck Plates

Create a vector of plate IDs to toss, pending QC crosschecks.

plate_ids_qcxcheck <- c(64461, 64462, 64480, 64482, 64483, 64484, 64485, 64486, 64502, 64503, 64509, 64511, 64512, 64513, 64514, 64515, 64542, 64543, 64582, 56362, 56363) %>% as.character()

Get sample size by silly.

(
  sample_size_qcxcheck <- dplyr::tibble(silly = sillyvec) %>%
    dplyr::mutate(genotyped = GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n))
)

Remove any fish on the plates pending QC crosschecks.

x <- sapply(sillyvec, function(silly) {
  my.silly <- get(paste0(silly, ".gcl"))
  my.silly <- my.silly %>% dplyr::filter(!stringr::str_detect(string = PLATE_ID, pattern = stringr::str_c(plate_ids_qcxcheck, collapse = "|")))
  assign(x = paste0(silly, ".gcl"),
         value = my.silly,
         pos = 1)
})
rm(x)  # suppresses output

How many fish were removed by silly?

(
  sample_size_qcxcheck <- sample_size_qcxcheck %>%
    dplyr::mutate(qcxcheck = genotyped - GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n))
)

How many total samples?

sum(sample_size_qcxcheck$qcxcheck)
[1] 1995

Okay, that looks about right for 21 plates. Must have been some partial plates.

Genotype QA

Standard data QA (GT-seq, no conScore):

  • Remove fish missing genotypes (<80% loci)
  • Remove contamination (heterozygosity outliers +/- 3.5 modified z-score)
  • Remove duplicates (>95% loci concordance, remove both duplicates)
(
  sample_size_qa <- dplyr::tibble(silly = sillyvec) %>%
    dplyr::mutate(genotyped = GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n))
)

Missing Loci (<80%)

Remove fish with <80% loci genotyped.

miss_loci <-
  GCLr::remove_ind_miss_loci(sillyvec = sillyvec,
                             proportion = 0.8)
G3;30 IDs were removed from CMADMCR13.gcl
gG3;30 IDs were removed from CMADMCR17.gcl
gG3;4 IDs were removed from CMADMCR18.gcl
gG3;6 IDs were removed from CMFISHCR13.gcl
gG3;110 IDs were removed from CMFISHCR14.gcl
gG3;111 IDs were removed from CMFISHCR17.gcl
gG3;79 IDs were removed from CMFISHCR18.gcl
gG3;182 IDs were removed from CMFISHCR19.gcl
gG3;6 IDs were removed from CMFISHCR21.gcl
gG3;29 IDs were removed from CMFISHCR22.gcl
gG3;36 IDs were removed from CMFISHCR23.gcl
gG3;2 IDs were removed from CMFISHCRT13.gcl
gG3;2 IDs were removed from CMFISHCRT21.gcl
gG3;3 IDs were removed from CMFISHCRT22.gcl
gG3;1 IDs were removed from CMFISHCRT23.gcl
gG3;42 IDs were removed from CMPROSCR13.gcl
gG3;5 IDs were removed from CMPROSCR14.gcl
gG3;17 IDs were removed from CMPROSCR17.gcl
gG3;8 IDs were removed from CMPROSCR18.gcl
gG3;13 IDs were removed from CMPROSCR19.gcl
gG3;2 IDs were removed from CMPROSCR21.gcl
gG3;9 IDs were removed from CMPROSCR22.gcl
gG3;2 IDs were removed from CMPROSCRT22.gcl
gG3;23 IDs were removed from CMSAWCR13.gcl
gG3;1 IDs were removed from CMSAWCR14.gcl
gG3;6 IDs were removed from CMSAWCR15.gcl
gG3;46 IDs were removed from CMSAWCR17.gcl
gG3;17 IDs were removed from CMSAWCR18.gcl
gG3;17 IDs were removed from CMSAWCR19.gcl
gG3;8 IDs were removed from CMSAWCR21.gcl
gG3;25 IDs were removed from CMSAWCR22.gcl
gG3;A total of 872 individuals were removed from sillys in sillyvec.
g
# miss_loci$IDs_Removed

GCLr::save_objects(objects = "miss_loci", path =  "../objects", rds = TRUE)

(
  sample_size_qa <- sample_size_qa %>%
    dplyr::mutate(missing = genotyped - GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n))
)

Overall 872 samples dropped due to poor quality DNA.

What was genotyping success by spawning state?

miss_loci_tib <- tibble::enframe(miss_loci$IDs_Removed, name = "silly", value = "fish_id") %>% 
  tidyr::unnest_longer(fish_id) %>% 
  dplyr::left_join(y = metadata, by = dplyr::join_by(silly, fish_id))

metadata %>% 
  dplyr::filter(genotyped) %>% 
  dplyr::mutate(drop_miss = silly_source %in% miss_loci_tib$silly_source) %>% 
  dplyr::count(drop_miss, spawning_state) %>% 
  tidyr::pivot_wider(names_from = drop_miss, values_from = n) %>% 
  dplyr::rename(n_drop = `TRUE`, n_keep = `FALSE`) %>% 
  dplyr::mutate(n = n_keep + n_drop,
                p_keep = n_keep / n,
                spawning_state = factor(spawning_state, levels = c("Alive", "Pink Gill", "Grey Gill", "Rotting"))) %>% 
  dplyr::arrange(spawning_state)

As expected, genotyping success was much lower for rotting fish.

Contamination (Heterozygosity > +/- 3.5 modified Z-score)

Remove contaminated samples prior to checking for duplicates!

Individual heterozygosity outliers is our best metric for removing contaminated samples in the absence of something more sophisticated like conScore from GTscore. In previous analyses, including Shedd et al. 2022, we’ve used the standard 1.5 IQR outlier detection method to remove excessively heterozygous individuals. However, the 1.5 IQR method assumes a normal distribution, but heart sample heterozygosities tend to be skewed right due to contamination. As of 2024-02-08, we decided to pivot to using modified Z-scores with cutoffs of +/- 3.5 as recommended by Iglewicz and Hoaglin 1. The modified Z-score uses the median and median absolute deviation (MAD) instead of the mean and standard deviation, and is thus more robust to outliers and asymmetrical distributions.

Source function and calculate individual heterozygosities from AHRP-PWS-Pink-Salmon-Pedigrees GitHub repo.

source("~/GitHub_repos/AHRP-PWS-Pink-Salmon-Pedigrees/code/functions/calc_ind_het.R")

(
  ind_het <-
    calc_ind_het(
      sillyvec = sillyvec,
      loci = loci251,
      ncores = parallel::detectCores() - 4
    )
)
Time difference of 17.24236 secs

Calculate +/- 3.5 modified z-score cutoffs by stream. NOTE this means that the exact heterozygosity cutoff will vary slightly by stream. For PWS Pink Salmon, I did it separately by silly, but some of our sample sizes are pretty small here and we have the separate “tag” sillys. I’m opting to do this by stream to allow for subtle changes in allele frequency per stream.

# Function to calculate median absolute deviation (MAD)
mad <- function(x) {
  median(abs(x - median(x)))
}

# Calculate the modified z-score for each individual
(
  ind_het <- ind_het %>%
    dplyr::mutate(
      year = 2000 + as.numeric(stringr::str_sub(
        string = silly,
        start = -2,
        end = -1
      )),
      stream = dplyr::case_when(
        stringr::str_detect(string = silly, pattern = "ADM") ~ "Admiralty",
        stringr::str_detect(string = silly, pattern = "FISH") ~ "Fish",
        stringr::str_detect(string = silly, pattern = "PROS") ~ "Prospect",
        stringr::str_detect(string = silly, pattern = "SAW") ~ "Sawmill",
        TRUE ~ "mistakes_were_made"
      )
    ) %>%
    dplyr::group_by(stream) %>%  # cutoffs are specific to each stream!
    dplyr::mutate(
      modified_z_score = 0.6745 * (het - median(het)) / mad(het),
      outlier = dplyr::case_when(abs(modified_z_score) > 3.5 ~ TRUE, TRUE ~ FALSE)
    ) %>%
    dplyr::ungroup()
)
  
GCLr::save_objects("ind_het", path = "objects", rds = TRUE)

Plot distribution of heterozygosity and show outliers.

ind_het %>% 
  ggplot2::ggplot(ggplot2::aes(x = het, fill = outlier)) +
  ggplot2::geom_histogram(binwidth = 1 / length(loci251)) +
  ggplot2::facet_grid(rows = ggplot2::vars(stream), scales = "free_y") +
  ggplot2::xlim(0, 1) +
  ggplot2::scale_fill_manual(name = "Outlier", values = c("TRUE" = "black", "FALSE" = "grey60")) +
  ggplot2::xlab("Individual Heterozygosity") +
  ggplot2::ylab("Frequency") +
  ggplot2::ggtitle("Individual Heterozygosity - By Stream") +
  ggplot2::theme_bw(base_size = 14) 

Remove outliers.

output <- lapply(sillyvec, function(x) {
  GCLr::remove_ids(
    silly = x,
    IDs = ind_het %>% dplyr::filter(outlier, silly == x) %>% dplyr::pull(fish_id)
  )
})
G3;8 IDs were removed from CMADMCR13.gcl
gG3;5 IDs were removed from CMADMCR14.gcl
gG3;147 IDs were removed from CMADMCR17.gcl
gG3;2 IDs were removed from CMADMCR18.gcl
gG3;11 IDs were removed from CMFISHCR13.gcl
gG3;113 IDs were removed from CMFISHCR14.gcl
gG3;111 IDs were removed from CMFISHCR17.gcl
gG3;49 IDs were removed from CMFISHCR18.gcl
gG3;55 IDs were removed from CMFISHCR19.gcl
gG3;2 IDs were removed from CMFISHCR20.gcl
gG3;7 IDs were removed from CMFISHCR21.gcl
gG3;17 IDs were removed from CMFISHCR22.gcl
gG3;100 IDs were removed from CMFISHCR23.gcl
gG3;1 IDs were removed from CMFISHCRT13.gcl
gG3;0 IDs were removed from CMFISHCRT21.gcl
gG3;1 IDs were removed from CMFISHCRT22.gcl
gG3;3 IDs were removed from CMFISHCRT23.gcl
gG3;18 IDs were removed from CMPROSCR13.gcl
gG3;10 IDs were removed from CMPROSCR14.gcl
gG3;153 IDs were removed from CMPROSCR17.gcl
gG3;6 IDs were removed from CMPROSCR18.gcl
gG3;50 IDs were removed from CMPROSCR19.gcl
gG3;0 IDs were removed from CMPROSCR20.gcl
gG3;2 IDs were removed from CMPROSCR21.gcl
gG3;10 IDs were removed from CMPROSCR22.gcl
gG3;0 IDs were removed from CMPROSCRT21.gcl
gG3;0 IDs were removed from CMPROSCRT22.gcl
gG3;17 IDs were removed from CMSAWCR13.gcl
gG3;2 IDs were removed from CMSAWCR14.gcl
gG3;4 IDs were removed from CMSAWCR15.gcl
gG3;93 IDs were removed from CMSAWCR17.gcl
gG3;1 IDs were removed from CMSAWCR18.gcl
gG3;0 IDs were removed from CMSAWCR19.gcl
gG3;0 IDs were removed from CMSAWCR20.gcl
gG3;2 IDs were removed from CMSAWCR21.gcl
gG3;0 IDs were removed from CMSAWCR22.gcl
gG3;0 IDs were removed from CMSAWCRT21.gcl
gG3;0 IDs were removed from CMSAWCRT22.gcl
g
message(paste0("A total of ", ind_het %>% dplyr::filter(outlier) %>% nrow(), " individuals were removed from sillys in sillyvec."))
G3;A total of 1000 individuals were removed from sillys in sillyvec.
g

Not too many contaminated samples! Most likely culled during missing loci.

(
  sample_size_qa <- sample_size_qa %>%
    dplyr::mutate(heterozygosity = genotyped - missing - GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n))
)

Overall 1,000 samples dropped due to sample contamination.

Duplicate (>=95%)

Identify potential duplicate genotypes (>=95% loci).

duplicate_check_95 <-
  GCLr::dupcheck_within_silly(
    sillyvec = sillyvec,
    minproportion = 0.95,
    minnonmissing = 0.6,
    ncores = parallel::detectCores() - 4
  )
Time difference of 21.62875 secs
GCLr::save_objects("duplicate_check_95", path = "objects", rds = TRUE)

How many duplicate pairs by silly?

duplicate_check_95 %>% 
  dplyr::count(silly) %>% 
  dplyr::arrange(dplyr::desc(n))

Remove both duplicates! As opposed to GSI work, where we want to keep individuals but aren’t typically worried about paired data, here we want to remove both individuals as the paired data integrity (including otolith reads!) is lost.

duplicates_removed <-
  GCLr::remove_dups(dupcheck = duplicate_check_95, remove_both = TRUE)
G3;2 IDs were removed from CMADMCR14.gcl
gG3;20 IDs were removed from CMADMCR17.gcl
gG3;6 IDs were removed from CMADMCR18.gcl
gG3;2 IDs were removed from CMFISHCR13.gcl
gG3;12 IDs were removed from CMFISHCR14.gcl
gG3;14 IDs were removed from CMFISHCR17.gcl
gG3;38 IDs were removed from CMFISHCR18.gcl
gG3;18 IDs were removed from CMFISHCR19.gcl
gG3;4 IDs were removed from CMFISHCR23.gcl
gG3;2 IDs were removed from CMFISHCRT23.gcl
gG3;2 IDs were removed from CMPROSCR13.gcl
gG3;4 IDs were removed from CMPROSCR14.gcl
gG3;14 IDs were removed from CMPROSCR17.gcl
gG3;4 IDs were removed from CMPROSCR18.gcl
gG3;10 IDs were removed from CMPROSCR19.gcl
gG3;4 IDs were removed from CMPROSCR20.gcl
gG3;2 IDs were removed from CMPROSCR22.gcl
gG3;8 IDs were removed from CMSAWCR17.gcl
gG3;4 IDs were removed from CMSAWCR18.gcl
gG3;2 IDs were removed from CMSAWCR22.gcl
g
GCLr::save_objects("duplicates_removed", path = "objects", rds = TRUE)

(
  sample_size_qa <- sample_size_qa %>%
    dplyr::mutate(
      duplicate = genotyped - missing - heterozygosity - GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n)
    )
)

Overall 157 samples dropped due to duplicate genotypes.

Final

Final, post-QA sample sizes by silly.

(
  sample_size_qa <- sample_size_qa %>%
    dplyr::mutate(
      final =  GCLr::silly_n(sillyvec = sillyvec) %>% dplyr::pull(n)
    )
)

GCLr::save_objects("sample_size_qa", path = "objects", rds = TRUE)

fs::dir_create("output")
readr::write_csv(x = sample_size_qa, file = "output/sample_size_qa.csv")

Save

Save final genotypes and update metadata.

Genotypes

Save post-QA genotypes with joined metadata.

fs::dir_create("data/genotypes/postQA")
GCLr::save_sillys(sillyvec = sillyvec, path = "data/genotypes/postQA", rds = TRUE)

Metadata

add pass_QA

postQA_silly_source <- sapply(sillyvec, function(silly) {get(paste0(silly, ".gcl"))$SillySource}) %>% unlist()

metadata <- metadata %>% 
  dplyr::mutate(pass_QA = silly_source %in% postQA_silly_source)

Confirm.

metadata %>% 
  dplyr::count(genotyped, pass_QA)

NOTE that genotyped in sample_size_qa doesn’t match genotyped in metadata because I pre-filtered out QC Crosscheck plates.

sample_size_qa %>% 
  janitor::adorn_totals() %>% 
  dplyr::filter(silly == "Total")
 silly genotyped missing heterozygosity duplicate final
 Total     16815     872           1000       157 14786

QA Conclusions

Overall 18,810 total pedigree samples were genotyped; 1,995 were removed due to pending QC Crosschecks; 872 were removed due to poor quality DNA (missing >= 20% loci); 1,000 were removed due to contaminated DNA (heterozygosity >= 3.5 modified z-score); and 157 were removed due to duplicate genotypes (>= 95% loci), leaving a total of 14,786 in the final dataset.

Adaptive Loci Genotypes

Create a single object with just the metadata and adaptive loci genotypes.

Calculate Allele Frequencies

Calculate the allele counts for each locus by stream, year, and DOY. Adapted from Pat Barry’s code in AHRP_ChumAdaptive.html

loci_adaptive <- grep(pattern = "OkeV2", x = loci251, value = TRUE)

# testing
# chum_adaptive_geno %>%
#   dplyr::select(silly_source, stream, year, DOY, tidyselect::starts_with(loci_adaptive[1])) %>%
#   tidyr::pivot_longer(cols = tidyselect::starts_with(loci_adaptive[1]), names_to = "locus", values_to = "allele") %>%
#   dplyr::mutate(allele = factor(allele),
#                 allele = as.numeric(allele)) %>%  # make factor and convert to numeric
#   dplyr::count(stream, year, DOY, allele) %>%
#   dplyr::mutate(locus = loci_adaptive[1])

# calculate the number of each allele observed per stream, year, day
(chum_adaptive_allele_counts <- lapply(loci_adaptive, function(locus) {
  chum_adaptive_geno %>%
    dplyr::select(silly_source,
                  stream,
                  year,
                  DOY,
                  tidyselect::starts_with(locus)) %>%
    tidyr::pivot_longer(
      cols = tidyselect::starts_with(locus),
      names_to = "locus",
      values_to = "allele"
    ) %>%
    dplyr::mutate(allele = factor(allele),
                allele = as.numeric(allele)) %>%  # make factor and convert to numeric
    dplyr::count(stream, year, DOY, allele) %>%
    dplyr::mutate(locus = locus)
}) %>%
    dplyr::bind_rows())

Convert to alelle frequencies.

(
  chum_adaptive_allele_freq <- chum_adaptive_allele_counts %>%
    tidyr::pivot_wider(names_from = allele, values_from = n) %>%
    dplyr::select(-`NA`) %>%  # drop no-calls
    dplyr::mutate(
      `1` = replace_na(`1`, 0),
      # account for unobserved alleles on a given day
      `2` = replace_na(`2`, 0)
    ) %>%
    tidyr::pivot_longer(
      cols = `1`:`2`,
      names_to = "allele",
      values_to = "n"
    ) %>%
    dplyr::group_by(stream, year, DOY, locus) %>%
    dplyr::mutate(
      n_alleles = sum(n),
      p_alleles = n / n_alleles,
      SNP = paste(locus, allele, sep = "_")
    ) %>% 
    dplyr::ungroup()
)

View Allele Frequencies

#  TODO
# double check allele frequency code to make sure it is correct
# make sure alleles are being assigned 1 vs. 2 consistently
# filter out hatchery strays?
# check sample sizes by stream/year/DOY?
# see if spawning state correlates?

Admiralty

chum_adaptive_allele_freq %>% 
  dplyr::filter(stream == "Admiralty") %>% 
  dplyr::mutate(locus = stringr::str_remove_all(string = locus, pattern = "OkeV2_")) %>% 
  ggplot2::ggplot(ggplot2::aes(x = DOY, y = p_alleles, colour = SNP, group_by(SNP))) +
  ggplot2::geom_point() +
  ggplot2::geom_smooth(method = "lm", weight = "n_alleles", formula = y~x) +
  ggplot2::facet_grid(locus ~ year) + 
  ggplot2::theme_bw() + 
  ggplot2::theme(legend.position = 'none')

Fish

chum_adaptive_allele_freq %>% 
  dplyr::filter(stream == "Fish - Douglas Island") %>% 
  dplyr::mutate(locus = stringr::str_remove_all(string = locus, pattern = "OkeV2_")) %>% 
  ggplot2::ggplot(ggplot2::aes(x = DOY, y = p_alleles, colour = SNP, group_by(SNP))) +
  ggplot2::geom_point() +
  ggplot2::geom_smooth(method = "lm", weight = "n_alleles", formula = y~x) +
  ggplot2::facet_grid(locus ~ year) + 
  ggplot2::theme_bw() + 
  ggplot2::theme(legend.position = 'none')

Prospect

chum_adaptive_allele_freq %>% 
  dplyr::filter(stream == "Prospect") %>% 
  dplyr::mutate(locus = stringr::str_remove_all(string = locus, pattern = "OkeV2_")) %>% 
  ggplot2::ggplot(ggplot2::aes(x = DOY, y = p_alleles, colour = SNP, group_by(SNP))) +
  ggplot2::geom_point() +
  ggplot2::geom_smooth(method = "lm", weight = "n_alleles", formula = y~x) +
  ggplot2::facet_grid(locus ~ year) + 
  ggplot2::theme_bw() + 
  ggplot2::theme(legend.position = 'none')

Sawmill

chum_adaptive_allele_freq %>% 
  dplyr::filter(stream == "Sawmill") %>% 
  dplyr::mutate(locus = stringr::str_remove_all(string = locus, pattern = "OkeV2_")) %>% 
  ggplot2::ggplot(ggplot2::aes(x = DOY, y = p_alleles, colour = SNP, group_by(SNP))) +
  ggplot2::geom_point() +
  ggplot2::geom_smooth(method = "lm", weight = "n_alleles", formula = y~x) +
  ggplot2::facet_grid(locus ~ year) + 
  ggplot2::theme_bw() + 
  ggplot2::theme(legend.position = 'none')

end


  1. Boris Iglewicz and David Hoaglin (1993), “Volume 16: How to Detect and Handle Outliers”, The ASQC Basic References in Quality Control: Statistical Techniques, Edward F. Mykytka, Ph.D., Editor.↩︎

LS0tDQp0aXRsZTogIkV4cG9ydCBHZW5vdHlwZXMgZm9yIDkgQWRhcHRpdmUgUnVuIFRpbWluZyBMb2NpIHRvIE5PQUEiDQpzdWJ0aXRsZTogIkZpbHRlcmVkIGZvciBQbGF0ZXMgTm90IE5lZWRpbmcgUUMgQ3Jvc3NjaGVja3MiDQphdXRob3I6ICJLeWxlIFNoZWRkIg0KZGF0ZTogIlN0YXJ0ZWQ6IDIwMjUtMDctMjQsIGxhc3Qgb3BlbmVkOiBgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0aGVtZTogdW5pdGVkDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHRydWUNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCiMgU2V0dXANCg0KYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0Kcm0obGlzdCA9IGxzKGFsbC5uYW1lcyA9IFRSVUUpKQ0KDQppZighcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpOyBsaWJyYXJ5KHBhY21hbikNCg0KcGFjbWFuOjpwX2xvYWQoDQogIHRpZHl2ZXJzZSwNCiAgc2NhbGVzLA0KICBqYW5pdG9yLA0KICBHQ0xyLA0KICBjb2luDQopDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGggPSAxMCkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCg0KLnVzZXJuYW1lID0gcmVhZExpbmVzKCJ+L3Vzcl9wdy50eHQiLCBuID0gMSkgICMgTE9LSSB1c2VybmFtZQ0KLnBhc3N3b3JkID0gcmVhZExpbmVzKCJ+L3Vzcl9wdy50eHQiICwgbiA9IDIpW1syXV0gICMgTE9LSSBwYXNzd29yZA0KYGBgDQoNCiMgT2JqZWN0aXZlDQoNClRoZSBwdXJwb3NlIG9mIHRoaXMgbm90ZWJvb2sgaXMgdG8gcHJvdmlkZSBXZXMgTGFyc29uIChOT0FBIEFCTCkgd2l0aCBhZGFwdGl2ZSBydW4gdGltaW5nIGdlbm90eXBlcyAoOSBsb2NpKSBmb3IgQUhSUCBjaHVtIHNhbXBsZXMuIEZpbHRlciBvdXQgcGxhdGVzIHVuZGVyZ29pbmcgUUMgY3Jvc3NjaGVja3MgZHVlIHRvIHBvb3IgZ2Vub3R5cGluZyBzdWNjZXNzLiBXZXMgaXMgaW50ZXJlc3RlZCBpbiByZXByZXNlbnRhdGlvbiBhY3Jvc3MgeWVhcnMsIGxlc3MgYWNyb3NzIG11bHRpcGxlIHN0cmVhbXMuIEhlIHdhbnRzIHRvIHNlZSBob3cgdGhlIHBhdHRlcm5zIGhvbGQgdXAgb3ZlciB0aW1lLg0KDQojIyBTdGVwcw0KDQogICogaW1wb3J0IGFsbCBleGlzdGluZyBnZW5vdHlwZXMgZm9yIENNMDY0LUNNMDcwDQogICogZmlsdGVyIG91dCB1bnRydXN0d29ydGh5IHBsYXRlcyB0aGF0IG5lZWQgUUMgY3Jvc3MgY2hlY2tzDQogICogc3RhbmRhcmQgR1Qtc2VxIGdlbm90eXBlIFFBIChtaXNzaW5nLCBoZXRlcm96eWdvc2l0eSwgZHVwbGljYXRlKQ0KICAqIGpvaW4gc2FtcGxlIGRhdGUgYW5kIHNwYXduaW5nIHN0YXRlIDwiLi4vT2NlYW5BSy9BSFJQIFNhbG1vbiBCaW9sb2dpY2FsIERhdGEgMjAyNDAxMDVfMTIyNDA0LjM2ODQ1OV93aXRoX3NwYXduaW5nX3N0YXRlLmNzdiI+DQogICogZmlsdGVyIGZvciA5IGFkYXB0aXZlIHJ1biB0aW1pbmcgbG9jaQ0KICAqIGV4cG9ydCBnZW5vdHlwZXMgd2l0aCBkYXRlL3NwYXduaW5nIHN0YXRlDQoNCiMgQmFja2dyb3VuZA0KDQpBbiBhZGFwdGl2ZSBtYXJrZXIgcGFuZWwgYXNzb2NpYXRlZCB3aXRoIHJ1biB0aW1pbmcgdmFyaWF0aW9uIGluIGNodW0gc2FsbW9uIHdhcyBkZXZlbG9wZWQgYnkgTk9BQSBBQkwgYW5kIHVzZWQgdG8gZ2Vub3R5cGUgdHdvIHBsYXRlcyBvZiBzYW1wbGVzIGZyb20gdGhlIEFERkcgQWxhc2thIEhhdGNoZXJ5IFJlc2VhcmNoIFByb2dyYW0gKEFIUlApIHN0dWR5IGJlaW5nIGNvbmR1Y3RlZCB3aXRoaW4gU0UgQWxhc2thLiBBSFJQIHNhbXBsZXMgKEZpc2ggQ3JlZWsgLSBEb3VnbGFzIElzbGFuZCAyMDIzICsgUHJvc3BlY3QgQ3JlZWsgMjAxNy8yMDE4KSB3ZXJlIGdlbm90eXBlZCBpbiBBdWd1c3QgMjAyNCBmb3IgMjAgc2luZ2xlIG51Y2xlb3RpZGUgcG9seW1vcnBoaXNtcyBhbmQgd2VpZ2h0ZWQgbGluZWFyIHJlZ3Jlc3Npb25zIHdlcmUgcGVyZm9ybWVkIHRvIGFzc2VzcyB0aGUgYXNzb2NpYXRpb24gb2YgZGlmZmVyZW50IFNOUHMgd2l0aCBydW4gdGltaW5nIChkYXRlIG9mIGNvbGxlY3Rpb24pLiBUaGUgY2h1bSBzYWxtb24gYWRhcHRpdmUgbWFya2VyIHBhbmVsIGNvbnRhaW5zIDIwIGxvY2kgc3ByZWFkIGFtb25nIDUgY2hyb21vc29tZXMgYW5kIHdhcyBkZXZlbG9wZWQgZnJvbSB2YXJpYXRpb24gZGV0ZWN0ZWQgYmV0d2VlbiBZdWtvbiBSaXZlciBzdW1tZXIgKEdpc2FzYSBSaXZlcikgYW5kIGZhbGwgKFBlbGx5IFJpdmVyKSBydW4gY2h1bSBzYWxtb24gd2l0aCBsb3cgY292ZXJhZ2Ugd2hvbGUgZ2Vub21lIHNlcXVlbmNpbmcuVGhlc2UgcG9wdWxhdGlvbnMgc3Bhd24gYXBwcm94aW1hdGVseSB0d28gbW9udGhzIGFuZCBvdmVyIDE1MDAgcml2ZXIga20gYXBhcnQuIE5PQUEgdGFyZ2V0ZWQgdGhlIG1ham9yIHBlYWtzIGluIGRpZmZlcmVudGlhdGlvbiB3aXRoIHBhcnRpY3VsYXIgaW50ZXJlc3QgaW4gbWFya2VycyB3aXRoaW4gdGhlIGdlbmVzIGxldWNpbmUgcmljaCByZXBlYXQgY29udGFpbmluZyBuaW5lIChMUlJDOTsgTEczNSkgYW5kIGVzdHJvZ2VuIHJlY2VwdG9yIM6yIChFU1JCOyBMRzI5KSBhcyB0aGV5IHdlcmUgYXNzb2NpYXRlZCB3aXRoIHJldHVybiB0aW1pbmcgaW4gb3RoZXIgc2FsbW9uaWQgc3BlY2llcy4gRWlnaHQgU05QcyAoZm91ciBvbiBMRzI5IGFuZCBmb3VyIG9uIExHMzUpIGFwcGVhciB0byBiZSBhc3NvY2lhdGVkIHdpdGggcnVuIHRpbWluZyB3aXRoaW4gdGhlc2UgY29sbGVjdGlvbnMuIFRoZSBzdHJlbmd0aCBvZiBhc3NvY2lhdGlvbiBmb3IgQUhSUCBzYW1wbGVzIGFwcGVhcnMgdG8gdmFyeSBieSBwb3B1bGF0aW9uLiBObyBtYXJrZXJzIG9uIExHcyA1LCAyMyBvciAxNSBhcHBlYXIgdG8gaGF2ZSBhIHN0cm9uZyBhc3NvY2lhdGlvbiB3aXRoIHJ1biB0aW1pbmcgd2l0aGluIHRoZXNlIGNvbGxlY3Rpb25zLiBPdmVyYWxsLCB0aGUgcmVzdWx0cyBmcm9tIExHMjkgYW5kIExHMzUgYXJlIHByb21pc2luZyBhbmQgdmFyaWF0aW9uIGluIHRoZXNlIFNOUHMgc2hvdWxkIGJlIHN1cnZleWVkIGZvciBhIGxhcmdlciBjb2xsZWN0aW9uIG9mIHNhbXBsZXMuDQoNCkEgdG90YWwgb2YgOSBhZGFwdGl2ZSwgcnVuIHRpbWluZyBsb2NpIHdlcmUgYWRkZWQgdG8gdGhlIEFIUlAgU0VBSyBDaHVtIHBhcmVudGFnZSBwYW5lbDogDQoNCiAgKiBPa2VWMl9MRzIzXzEwNTQ2MjM3DQogICogT2tlVjJfTEcyOV8yNTQ1MDc1Mg0KICAqIE9rZVYyX0xHMjlfMjU0NTU4MzMNCiAgKiBPa2VWMl9MRzI5XzI1NDgzNTk1DQogICogT2tlVjJfTEcyOV8yNTUxNTE2MQ0KICAqIE9rZVYyX0xHMzVfMjgwNzg4NjcNCiAgKiBPa2VWMl9MRzM1XzI4MTI4Njg3DQogICogT2tlVjJfTEczNV8yODE2NTA3Ng0KICAqIE9rZVYyX0xHMzVfMjgxNjcxNzINCg0KQWxsIEFIUlAgQ2h1bSBzYW1wbGVzIGZyb20gRmlzaCBDcmVlayAtIERvdWdsYXMgSXNsYW5kLCBQcm9zcGVjdCBDcmVlaywgQWRtaXJhbHR5IENyZWVrLCBhbmQgU2F3bWlsbCBDcmVlayB3ZXJlIGdlbm90eXBlZCBmb3IgdGhlIHBhcmVudGFnZSBwYW5lbCBhdCB0aGUgQURGJkcgR2VuZSBDb25zZXJ2YXRpb24gTGFib3JhdG9yeSAoR0NMKSBpbiBXaW50ZXIvU3ByaW5nIDIwMjUuIER1ZSB0byBwb29yIHRpc3N1ZSBxdWFsaXR5LCB0aGVyZSB3YXMgYSBoaWdoIHByb3BvcnRpb24gb2Ygc2FtcGxlcyB0aGF0IGZhaWxlZCB0byBnZW5vdHlwZSBhbmQvb3IgYXBwZWFyIHRvIGJlIGNvbnRhbWluYXRlZCAoaGlnaCBoZXRlcm96eWdvc2l0eSksIHJlc3VsdGluZyBpbiBsb3cgUUMgcG93ZXIgZm9yIHNldmVyYWwgcGxhdGVzLiBRQyBjcm9zc2NoZWNrcyBhcmUgY3VycmVudGx5IHVuZGVyd2F5IGF0IHRoZSBHQ0wgaW4gSnVseSAyMDI1LCBob3dldmVyLCBXZXMgd2FudHMgd2hhdGV2ZXIgZ2Vub3R5cGVzIHdlIGhhdmUgb24gaGFuZCBub3cgc28gd2lsbCBqdXN0IGZpbHRlciBvdXQgdGhvc2UgcGxhdGVzLg0KDQojIEltcG9ydCBEYXRhDQoNCiMjICpMb2N1c0NvbnRyb2wqDQoNCkNyZWF0ZSBgb2JqZWN0c2AsIGNyZWF0ZSAqTG9jdXNDb250cm9sKiBmb3IgYENodW1fQUhSUF8yNTdfdjEuMy4wLmANCmBgYHtyIExvY3VzQ29udHJvbCwgZXZhbD1GQUxTRX0NCmZzOjpkaXJfY3JlYXRlKCJvYmplY3RzIikNCkdDTHI6OmNyZWF0ZV9sb2N1c2NvbnRyb2wobWFya2Vyc3VpdGUgPSAiQ2h1bV9BSFJQXzI1N192MS4zLjAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VybmFtZSA9IC51c2VybmFtZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzc3dvcmQgPSAucGFzc3dvcmQpDQpHQ0xyOjpzYXZlX29iamVjdHMob2JqZWN0cyA9ICJMb2N1c0NvbnRyb2wiLCBwYXRoID0gIm9iamVjdHMiKQ0KYGBgDQoNCkhybW0sIG9ubHkgMjUxIGxvY2kuIENvbmZpcm0gdGhhdCB0aGVzZSBhcmUgdGhlIHNhbWUgbG9jaSBydW4gaW4gdGhlIG1vc3QgcmVjZW50IEFIUlAgQ2h1bSBwcm9qZWN0IENNMDY5DQpgYGB7ciwgZXZhbD1GQUxTRX0NCnByb2JlX2xvY2lfQ00wNjkgPC0gcmVhZHI6OnJlYWRfdHN2KA0KICBmaWxlID0gIlY6L0xhYi9HZW5vdHlwaW5nL1NOUCBQcm9qZWN0cy9DaHVtL1Byb2plY3QgQ00wNjkgQUhSUCBQYXJlbnRhZ2UvR2Vub3R5cGUgRGF0YSBGaWxlcy9zZXQycnJfQ00wNjlfb3V0cHV0cy9wcm9iZXMudHh0IiwgDQogIHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JQ0KICBkcGx5cjo6ZGlzdGluY3QoYCNMb2N1c19JRGApICU+JQ0KICBkcGx5cjo6cHVsbCgpDQoNCmFsbC5lcXVhbChwcm9iZV9sb2NpX0NNMDY5LCBMb2N1c0NvbnRyb2wkbG9jdXNuYW1lcykNCmBgYA0KDQpZZXMsIGFsbCBhcmUgdGhlIHNhbWUuDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmxvY2kyNTEgPC0gTG9jdXNDb250cm9sJGxvY3VzbmFtZXMNCkdDTHI6OnNhdmVfb2JqZWN0cyhvYmplY3RzID0gImxvY2kyNTEiLCBwYXRoID0gIm9iamVjdHMiKQ0KYGBgDQoNCiMjIEdlbm90eXBlcw0KDQpSZWFkIGluIGdlbm90eXBlcyBieSBwcm9qZWMgdG8gZ2V0IHNpbGx5dmVjLg0KYGBge3IsIGV2YWw9RkFMU0V9DQpybShMb2N1c0NvbnRyb2wpDQojIEdDTHI6Omxva2kycl9wcm9qKHByb2plY3RfbmFtZSA9IHBhc3RlMCgiQ00wIiwgNjQ6NzApLCB1c2VybmFtZSA9IC51c2VybmFtZSwgcGFzc3dvcmQgPSAucGFzc3dvcmQpICAjIGNvbW1lbnQgb3V0IHRvIGF2b2lkIHJlLXJ1bm5pbmcNCmBgYA0KDQpXaGF0IGNvbGxlY3Rpb25zPw0KYGBge3IsIGV2YWw9RkFMU0V9DQooc2lsbHl2ZWMgPC0gc29ydChwcm9qZWN0X3NpbGx5cykpDQpgYGANCg0KU3Vic2V0IGBzaWxseXZlY2AgdG8gcmVtb3ZlIGFsZXZpbiBjb2xsZWN0aW9uDQpgYGB7ciwgZXZhbD1GQUxTRX0NCihzaWxseXZlYyA8LSBncmVwKHBhdHRlcm4gPSAiYSIsIHggPSBzaWxseXZlYywgaWdub3JlLmNhc2UgPSBGQUxTRSwgdmFsdWUgPSBUUlVFLCBpbnZlcnQgPSBUUlVFKSkNCmBgYA0KDQpXaXBlIG91dCBgLmdjbGAgb2JqZWN0cyBhbmQgcmUtcmVhZCBpbiB1c2luZyBgbG9raTJyYC4NCmBgYHtyLCBldmFsPUZBTFNFfQ0KR0NMcjo6c2F2ZV9vYmplY3RzKG9iamVjdHMgPSAic2lsbHl2ZWMiLCBwYXRoID0gIm9iamVjdHMiKQ0KDQpybShsaXN0ID0gb2JqZWN0cyhwYXR0ZXJuID0gIi5nY2wiKSkNCnJtKExvY3VzQ29udHJvbCkNCmBgYA0KDQpSZWFkIGluIGdlbm90eXBlcy4NCmBgYHtyIGV2YWw9RkFMU0V9DQpHQ0xyOjpsb2FkX29iamVjdHMocGF0aCA9ICJvYmplY3RzIiwgcGF0dGVybiA9ICJMb2N1c0NvbnRyb2wiKQ0KDQpHQ0xyOjpsb2tpMnIoc2lsbHl2ZWMgPSBzaWxseXZlYywNCiAgICAgICAgICAgICB1c2VybmFtZSA9IC51c2VybmFtZSwNCiAgICAgICAgICAgICBwYXNzd29yZCA9IC5wYXNzd29yZCwgDQogICAgICAgICAgICAgdGVzdF90eXBlID0gIkdUU05QIikNCmBgYA0KDQpPbmx5IHRvb2sgNS4zOSBtaW51dGVzIHRvIHJlYWQgZXZlcnl0aGluZyBpbiBhdCB0aGUgb2ZmaWNlIQ0KDQpTYXZlIHJhdyBnZW5vdHlwZXMgKHVzZWZ1bCBmb3Igd29ya2luZyBvZmZzaXRlIGFuZCBvZmYgVlBOKS4NCmBgYHtyIGV2YWw9RkFMU0V9DQpmczo6ZGlyX2NyZWF0ZSgiZGF0YS9nZW5vdHlwZXMiKQ0KZnM6OmRpcl9jcmVhdGUoImRhdGEvZ2Vub3R5cGVzL3JhdyIpDQojIEdDTHI6OnNhdmVfc2lsbHlzKHNpbGx5dmVjID0gc2lsbHl2ZWMsIHBhdGggPSAiZGF0YS9nZW5vdHlwZXMvcmF3IiwgcmRzID0gVFJVRSkgICMgaGlkZSBzbyB5b3UgZG9uJ3Qgb3ZlcndyaXRlIG9uIGFjY2lkZW50IQ0KYGBgDQoNCg0KIyMjIFJlLWxvYWQNCg0KUmUtbG9hZCwgaWYgbmVjZXNzYXJ5DQpgYGB7cn0NCkdDTHI6OmxvYWRfb2JqZWN0cyhwYXRoID0gIm9iamVjdHMiKQ0KR0NMcjo6bG9hZF9zaWxseXMoc2lsbHl2ZWMgPSBzaWxseXZlYywgcGF0aCA9ICJkYXRhL2dlbm90eXBlcy9yYXciLCByZHMgPSBUUlVFKQ0KYGBgDQoNCiMjIE1ldGFkYXRhDQoNClJlYWQgaW4gcGFpcmVkIG1ldGFkYXRhIGZyb20gU2FsbW9uIEJpb2xvZ2ljYWwgRmFjdCBhbmQgU3RyZWFtIFNwZWNpbWVucywgZm9ybWF0IHNpbWlsYXIgdG8gYHB3c19waW5rX3BlZF9kYXRgLiAqKk5PVEUqKiBJJ20gZG9pbmcgdGhpcyBpbiBhIGJpdCBvZiBhIGh1cnJ5LCBzbyBtYWtlIHN1cmUgdG8gcHJvb2YgcmVhZCBjYXJlZnVsbHkgaWYgcmVjeWNsaW5nIHRoaXMgY29kZSENCmBgYHtyfQ0KKG1ldGFkYXRhIDwtIHJlYWRyOjpyZWFkX2NzdihmaWxlID0gIi4uL09jZWFuQUsvQUhSUCBTYWxtb24gQmlvbG9naWNhbCBEYXRhIDIwMjQwMTA1XzEyMjQwNC4zNjg0NTlfd2l0aF9zcGF3bmluZ19zdGF0ZS5jc3YiKSkNCmBgYA0KDQpNb2RpZnkNCmBgYHtyfQ0KKA0KICBtZXRhZGF0YSA8LSBtZXRhZGF0YSAlPiUgDQogICAgZHBseXI6OnNlbGVjdCgtZHBseXI6OmNvbnRhaW5zKCJ0YXJnZXQiKSkgJT4lIA0KICAgIGRwbHlyOjpzZWxlY3QoLWRwbHlyOjpjb250YWlucygiZGV0ZXJtaW5hdGlvbiIpKSAlPiUgDQogICAgZHBseXI6OnJlbmFtZShkYXRlID0gc2FtcGxlX2RhdGUsDQogICAgICAgICAgICAgICAgICBsZW5ndGggPSBsZW5ndGhfbW0sDQogICAgICAgICAgICAgICAgICBzaWxseSA9IHNpbGx5X2NvZGUpICU+JSANCiAgICB0aWR5cjo6dW5pdGUoY29sID0gInNhbXBsZSIsIGRuYV90cmF5X2NvZGU6ZG5hX3RyYXlfd2VsbF9jb2RlLCBzZXAgPSAiXyIsIHJlbW92ZSA9IEZBTFNFKSAlPiUgDQogICAgdGlkeXI6OnVuaXRlKGNvbCA9ICJzaWxseV9zb3VyY2UiLCBjKHNpbGx5LCBmaXNoX2lkKSwgc2VwID0gIl8iLCByZW1vdmUgPSBGQUxTRSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUoZnJhbnpfaWQgPSBzdHJpbmdyOjpzdHJfYyhzdHJpbmdyOjpzdHJfc3ViKHNpbGx5LCAxLCAyKSwgICMgMXN0IGxldHRlciBvZiBzcGVjaWVzIG5hbWUsIDFzdCBsZXR0ZXIgb2Ygc3RyZWFtIG5hbWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5ncjo6c3RyX3N1YihzaWxseSwgLTIsIC0xKSwgICMgMiBkaWdpdCBzYW1wbGUgeWVhcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiXyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3I6OnN0cl9wYWQoZmlzaF9pZCwgd2lkdGggPSA1LCBwYWQgPSAiMCIsIHNpZGUgPSAibGVmdCIpKSwgICMgNSBkaWdpdCBmaXNoaWQgdXNpbmcgcGFkZGVkIHplcm9zIHVwIGZyb250KQ0KICAgICAgICAgICAgICAgICAgeWVhciA9IGx1YnJpZGF0ZTo6eWVhcihkYXRlKSwNCiAgICAgICAgICAgICAgICAgIHN0cmVhbSA9IHN0cmluZ3I6OnN0cl9yZW1vdmUoc3RyaW5nID0gbG9jYXRpb25fY29kZSwgcGF0dGVybiA9ICIgQ3JlZWsiKSwNCiAgICAgICAgICAgICAgICAgIERPWSA9IGx1YnJpZGF0ZTo6eWRheShkYXRlKSwgICMgZGF5IG9mIHllYXINCiAgICAgICAgICAgICAgICAgIHNleCA9IGRwbHlyOjpjYXNlX3doZW4oDQogICAgICAgICAgICAgICAgICAgIHNleCA9PSAiTSIgfiAiTWFsZSIsDQogICAgICAgICAgICAgICAgICAgIHNleCA9PSAiRiIgfiAiRmVtYWxlIiwNCiAgICAgICAgICAgICAgICAgICAgc2V4ID09ICJVIiB+IE5BX2NoYXJhY3Rlcl8sDQogICAgICAgICAgICAgICAgICAgIGlzLm5hKHNleCkgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICAgICAgICAgICAgICAgIG9yaWdpbiA9IGRwbHlyOjpjYXNlX3doZW4oDQogICAgICAgICAgICAgICAgICAgIG90b2xpdGhfbWFya19wcmVzZW50ID09ICJOTyIgfiAiTmF0dXJhbCIsDQogICAgICAgICAgICAgICAgICAgIG90b2xpdGhfbWFya19wcmVzZW50ID09ICJZRVMiIH4gIkhhdGNoZXJ5Ig0KICAgICAgICAgICAgICAgICAgKSwgICMgYWRkIG9yaWdpbiB2YXJpYWJsZQ0KICAgICAgICAgICAgICAgICAgb3JpZ2luID0gYmFzZTo6ZmFjdG9yKG9yaWdpbiwgbGV2ZWxzID0gYygiTmF0dXJhbCIsICJIYXRjaGVyeSIpKSwgICMgbWFrZSBmYWN0b3IgdG8gZW5zdXJlIGhhdGNoZXJ5ICE9IHJlZA0KICAgICAgICAgICAgICAgICAgc3Bhd25pbmdfc3RhdGUgPSBkcGx5cjo6Y2FzZV93aGVuKA0KICAgICAgICAgICAgICAgICAgICBzcGF3bmluZ19zdGF0ZSA9PSAiVW5rbm93biIgfiBOQV9jaGFyYWN0ZXJfLA0KICAgICAgICAgICAgICAgICAgICBUUlVFIH4gc3Bhd25pbmdfc3RhdGUNCiAgICAgICAgICAgICAgICAgICkgICMgZm9yY2UgVW5rbm93biB0byBOQQ0KICAgICkgJT4lICANCiAgICBkcGx5cjo6c2VsZWN0KA0KICAgICAgZnJhbnpfaWQsICAjIHVuaXF1ZSBpZGVudGlmaWVyIGZvciBGUkFOeiAoZS5nLiBQRTE2XzAwMDAxLCBzcGVjaWVzLCBzdHJlYW0sIHllYXIsIEdDTCBmaXNoX2lkKQ0KICAgICAgc2FtcGxlLCAgIyB1bHRpbWF0ZSBkYXRhIGtleSBmb3IgQUhSUCwgRFdQIGJhcmNvZGUgKyBEV1AgcG9zaXRpb24NCiAgICAgIHNpbGx5X3NvdXJjZSwgICMgdW5pcXVlIGlkZW50aWZpZXIgZm9yIExPS0kgKEdDTCBkYXRhYmFzZSkNCiAgICAgIHN0cmVhbSwgICMgNSBwZWRpZ3JlZSBzdHJlYW1zDQogICAgICB5ZWFyLCAgIyBzYW1wbGUgeWVhciAoYWthIGRlYXRoIHllYXIgZm9yIEZSQU56KQ0KICAgICAgb3JpZ2luLCAgIyBoYXRjaGVyeSBvciB3aWxkDQogICAgICAjIGhhdGNoZXJ5LCAgIyB3aGljaCBoYXRjaGVyeQ0KICAgICAgc2V4LCAgIyBtYWxlLCBmZW1hbGUsIHVua25vd24NCiAgICAgIGRhdGUsICAjIHNhbXBsZSBkYXRlDQogICAgICBET1ksICAjIGRheSBvZiB5ZWFyIChha2EganVsaWFuIHNhbXBsZSBkYXRlKQ0KICAgICAgbGVuZ3RoLCAgIyBtaWRleWUgdG8gaHlwdXJhbCBwbGF0ZSBsZW5ndGggaW4gbW0NCiAgICAgICMgaW50ZXJ0aWRhbCwgICMgc2FtcGxpbmcgbG9jYXRpb24gYWJvdmUgb3IgYmVsb3cgaW50ZXJ0aWRhbA0KICAgICAgIyBkaXN0YW5jZV9tb3V0aCwgICMgcml2ZXJkaXN0IGFwcHJveGltYXRlIHNhbXBsaW5nIGxvY2F0aW9uIGZyb20gbW91dGggaW4gbWV0ZXJzIA0KICAgICAgc3Bhd25pbmdfc3RhdGUsICAjIGFsaXZlLCBwaW5rIGdpbGwsIGdyZXkgZ2lsbCwgcm90dGluZyAoYWthIGluZGV4IG9mIHN0cmVhbSBsaWZlKQ0KICAgICAgIyBwcmVfc3Bhd24sICAjIFRSVUUvRkFMU0UgKGFkZGVkIGluIDIwMTYpOyBpZiB0aGUgZWdncyBhbmQgbWlsdCBkbyBub3QgZWFzaWx5IHJlbGVhc2UgYW5kIGNhcmNhc3MgaGFzIGNvbXBsZXRlbHkgZnVsbCBnb25hZHMNCiAgICAgICMgcGFydGlhbF9zcGF3biwgICMgVFJVRS9GQUxTRSAoYWRkZWQgaW4gMjAxNSk7IGZpc2ggaXMgc2FtcGxlZCAoZGVhZCBvciBhbGl2ZSkgd2l0aCBtb3JlIHRoYW4gYSBsaXR0bGUgZWdncyBvciBtaWx0DQogICAgICAjIHByZXllZF91cG9uLCAgIyBUUlVFL0ZBTFNFIChhZGRlZCBpbiAyMDE1KTsgZ29uYWRzIHJlbW92ZWQgYnkgcHJlZGF0b3JzDQogICAgICAjIHN0cmVhbV90cmliLCAgIyBzdHJlYW0gdHJpYnV0YXJ5IChHaWxtb3VyLCBQYWRkeSwgYW5kIFN0b2NrZGFsZSBoYXZlIG11bHRpcGxlKQ0KICAgICAgIyBsYXRpdHVkZSwgICMgc2FtcGxpbmcgYXJlYSAod2hlcmUgZmlzaCB3ZXJlIG1vdmVkIHRvIHByaW9yIHRvIHNhbXBsaW5nLCByb3VnaCBhcHByb3hpbWF0ZSBvZiBzcGF3bmluZyBsb2NhdGlvbikNCiAgICAgICMgbG9uZ2l0dWRlLCAgIyBzYW1wbGluZyBhcmVhICh3aGVyZSBmaXNoIHdlcmUgbW92ZWQgdG8gcHJpb3IgdG8gc2FtcGxpbmcsIHJvdWdoIGFwcHJveGltYXRlIG9mIHNwYXduaW5nIGxvY2F0aW9uKQ0KICAgICAgb3RvbGl0aF9tYXJrX3ByZXNlbnQsICAjIG90b2xpdGggbWFyayBwcmVzZW50ICh5ZXMgPSBoYXRjaGVyeSwgbm8gPSBuYXR1cmFsKQ0KICAgICAgb3RvbGl0aF9tYXJrX2lkLCAgIyBvdG9saXRoIGhhdGNoZXJ5IG1hcmsNCiAgICAgIG90b2xpdGhfbWFya19zdGF0dXNfY29kZSwgICMgb3RvbGl0aCByZWFkIHN0YXR1cyBjb2RlIChyZWFzb24gZm9yIG5vIHJlYWQgPSBvdmVyZ3JvdW5kLCBsb3N0LCBldGMuKQ0KICAgICAgc2lsbHksICAjIEdDTCBjb2xsZWN0aW9uIGNvZGUNCiAgICAgIGZpc2hfaWQsICAjIEdDTCBmaXNoIElEIHdpdGhpbiBhIHNpbGx5DQogICAgICBkbmFfdHJheV9jb2RlLCAgIyAxMC1kaWdpdCBEV1AgYmFyY29kZQ0KICAgICAgZG5hX3RyYXlfd2VsbF9jb2RlLCAgIyA0OCBEV1AgcG9zaXRpb24gMTo0OA0KICAgICAgIyBkbmFfdHJheV93ZWxsX3BvcywgICMgNDggRFdQIHBvc2l0aW9uIEExOkg2DQogICAgICAjIGRpc3RhbmNlX3RpZGUsICAjIHJpdmVyZGlzdCBhcHByb3hpbWF0ZSBzYW1wbGluZyBsb2NhdGlvbiBmcm9tIG1heGltdW0gaGlnaCB0aWRlDQogICAgICAjIHJpdmVyZGlzdF9zZWcsICAjIHJpdmVyZGlzdCByaXZlciBzZWdtZW50DQogICAgICAjIHJpdmVyZGlzdF92ZXJ0LCAgIyByaXZlcmRpc3QgdmVydGV4DQogICAgICAjIHJpdmVyZGlzdF9zbmFwZGlzdCwgICMgcml2ZXJkaXN0IHNuYXBwaW5nIGRpc3RhbmNlIChha2EgaG93IGZhciB3YXMgc2FtcGxpbmcgbG9jYXRpb24gbGF0L2xvbmcgZnJvbSB0aGUgc3RyZWFtIHBvbHlsaW5lKQ0KICAgICAgIyBoaWdoX3RpZGUgICMgaGlnaGVzdCBleHRlbnQgb2YgaGlnaCB0aWRlIGluIG1ldGVycyBmcm9tIG1vdXRoLCB1bmlxdWUgdG8gZWFjaCBzdHJlYW0NCiAgICAgIGZ3X2FnZSwNCiAgICAgIHN3X2FnZQ0KICAgICkgIA0KKSAgICAgICANCmBgYA0KDQojIFNhbXBsZSBTaXplcw0KDQpWZXJpZnkgdGhhdCBhbGwgZ2Vub3R5cGVkIHNhbXBsZXMgaGF2ZSBtZXRhZGF0YS4NCmBgYHtyfQ0KZ2Vub19zaWxseV9zb3VyY2UgPC0gc2FwcGx5KHNpbGx5dmVjLCBmdW5jdGlvbihzaWxseSkge2dldChwYXN0ZTAoc2lsbHksICIuZ2NsIikpJFNpbGx5U291cmNlfSkgJT4lIHVubGlzdCgpDQoNCnRhYmxlKGdlbm9fc2lsbHlfc291cmNlICVpbiUgbWV0YWRhdGEkc2lsbHlfc291cmNlKQ0KYGBgDQoNClNob290LCB3aGljaCBzYW1wbGVzIGRvIG5vdCBoYXZlIGFueSBtZXRhZGF0YT8NCg0KQWgsIEkgYmV0IHRoZXkgYXJlIHRoZSBGaXNoIENyZWVrIHRhZyBzYW1wbGVzIGZyb20gMjAxMyB0aGF0IFR5bGVyIERhbm4gYW5kIEhlYXRoZXIgTGlsbGVyIGNvbGxlY3RlZCENCmBgYHtyfQ0Kc2V0ZGlmZihnZW5vX3NpbGx5X3NvdXJjZSwgbWV0YWRhdGEkc2lsbHlfc291cmNlKQ0KYGBgDQoNCkNvcnJlY3QuIEFkZCB0aGVzZSB0byBtZXRhZGF0YS4NCmBgYHtyfQ0KbWV0YWRhdGEgPC0gDQogIGRwbHlyOjpiaW5kX3Jvd3MobWV0YWRhdGEsDQogICAgICAgICAgICAgICAgICAgQ01GSVNIQ1JUMTMuZ2NsICU+JSANCiAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoU0lMTFlfQ09ERSwgRktfRklTSF9JRCwgU2lsbHlTb3VyY2UsIEROQV9UUkFZX0NPREUsIEROQV9UUkFZX1dFTExfQ09ERSwgQ0FQVFVSRV9EQVRFKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICBkcGx5cjo6cmVuYW1lKHNpbGx5ID0gU0lMTFlfQ09ERSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlzaF9pZCA9IEZLX0ZJU0hfSUQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpbGx5X3NvdXJjZSA9IFNpbGx5U291cmNlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZG5hX3RyYXlfY29kZSA9IEROQV9UUkFZX0NPREUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRuYV90cmF5X3dlbGxfY29kZSA9IEROQV9UUkFZX1dFTExfQ09ERSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA9IENBUFRVUkVfREFURSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgdGlkeXI6OnVuaXRlKGNvbCA9ICJzYW1wbGUiLCBkbmFfdHJheV9jb2RlOmRuYV90cmF5X3dlbGxfY29kZSwgc2VwID0gIl8iLCByZW1vdmUgPSBGQUxTRSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgdGlkeXI6OnVuaXRlKGNvbCA9ICJzaWxseV9zb3VyY2UiLCBjKHNpbGx5LCBmaXNoX2lkKSwgc2VwID0gIl8iLCByZW1vdmUgPSBGQUxTRSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgZHBseXI6Om11dGF0ZShmcmFuel9pZCA9IHN0cmluZ3I6OnN0cl9jKHN0cmluZ3I6OnN0cl9zdWIoc2lsbHksIDEsIDIpLCAgIyAxc3QgbGV0dGVyIG9mIHNwZWNpZXMgbmFtZSwgMXN0IGxldHRlciBvZiBzdHJlYW0gbmFtZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3I6OnN0cl9zdWIoc2lsbHksIC0yLCAtMSksICAjIDIgZGlnaXQgc2FtcGxlIHllYXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiXyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5ncjo6c3RyX3BhZChmaXNoX2lkLCB3aWR0aCA9IDUsIHBhZCA9ICIwIiwgc2lkZSA9ICJsZWZ0IikpLCAgIyA1IGRpZ2l0IGZpc2hpZCB1c2luZyBwYWRkZWQgemVyb3MgdXAgZnJvbnQpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPSBsdWJyaWRhdGU6OmFzX2RhdGUoZGF0ZSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXIgPSBsdWJyaWRhdGU6OnllYXIoZGF0ZSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmVhbSA9ICJGaXNoIC0gRG91Z2xhcyBJc2xhbmQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBET1kgPSBsdWJyaWRhdGU6OnlkYXkoZGF0ZSkgICMgZGF5IG9mIHllYXINCiAgICAgICAgICAgICAgICAgICAgICkgDQopDQpgYGANCg0KVmVyaWZ5IGFsbCBzYW1wbGVzIGhhdmUgbWV0YWRhdGEuDQpgYGB7cn0NCnRhYmxlKGdlbm9fc2lsbHlfc291cmNlICVpbiUgbWV0YWRhdGEkc2lsbHlfc291cmNlKQ0KYGBgDQoNCkV4Y2VsbGVudCwgYWxsIHByZXNlbnQgYW5kIGFjY291bnRlZCBmb3IuDQoNClJlY29yZCB3aGljaCBzYW1wbGVzIHdlcmUgZ2Vub3R5cGVkLg0KYGBge3J9DQptZXRhZGF0YSA8LSBtZXRhZGF0YSAlPiUgDQogIGRwbHlyOjptdXRhdGUoZ2Vub3R5cGVkID0gc2lsbHlfc291cmNlICVpbiUgZ2Vub19zaWxseV9zb3VyY2UpDQpgYGANCg0KSG93IG1hbnkgc2FtcGxlcyBwZXIgY29sbGVjdGlvbiAoc2lsbHkpPw0KYGBge3J9DQptZXRhZGF0YSAlPiUgDQogIGRwbHlyOjpjb3VudChnZW5vdHlwZWQsIHNpbGx5KSAlPiUgDQogIHRpZHlyOjpwaXZvdF93aWRlcihuYW1lc19mcm9tID0gZ2Vub3R5cGVkLCB2YWx1ZXNfZnJvbSA9IG4pDQpgYGANCg0KIyMgQ2hlY2sgRm9yIFNhbXBsZSBEdXBsaWNhdGVzDQoNCkkgZ290IGEgd2VpcmQgbWFueS10by1tYW55IGVycm9yIGluIGEgam9pbiB0aGUgZmlyc3QgdGltZSBJIHJhbiB0aHJvdWdoIHRoaXMgc2NyaXB0LCBzbyBJIHdhbnQgdG8gY2hlY2sgYW5kIHNlZSBpZiB0aGVyZSBhcmUgYWN0dWFsbHkgZHVwbGljYXRlIHNhbXBsZXMgcHJlc2VudCBhbmQgaWYgc28sIHJlbW92ZSB0aGVtIQ0KYGBge3J9DQojIGJpbmQgYWxsIHNhbXBsZXMgdG9nZXRoZXIgaW50byBhIHNpbmdsZSAuZ2NsIG9iamVjdA0KbXkuZ2NsIDwtIHNhcHBseShzaWxseXZlYywgZnVuY3Rpb24oc2lsbHkpIHsNCiAgbXkuc2lsbHkgPC0gZ2V0KHBhc3RlMChzaWxseSwgIi5nY2wiKSkNCn0sIHNpbXBsaWZ5ID0gRkFMU0UpICU+JSANCiAgZHBseXI6OmJpbmRfcm93cygpDQpgYGANCg0KQW55IGR1cGxpY2F0ZSBgU2lsbHlTb3VyY2VgPw0KYGBge3J9DQp0YWJsZSh0YWJsZShteS5nY2wkU2lsbHlTb3VyY2UpKQ0KYGBgDQoNCkNsZWFuIHVwDQpgYGB7cn0NCnJtKG15LmdjbCkNCmBgYA0KDQpBcmUgdGhlcmUgbWV0YWRhdGEgZHVwbGljYXRlcz8NCmBgYHtyfQ0KdGFibGUodGFibGUobWV0YWRhdGEkc2lsbHlfc291cmNlKSkNCmBgYA0KDQpXZWxsIHNvbiBvZiBhIGJpc2N1aXQsIHRoZXJlIGFyZSENCmBgYHtyfQ0KbWV0YWRhdGEgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGR1cGxpY2F0ZWQoc2lsbHlfc291cmNlKSB8IGR1cGxpY2F0ZWQoc2lsbHlfc291cmNlLCBmcm9tTGFzdCA9IFRSVUUpKSAlPiUgDQogIGRwbHlyOjphcnJhbmdlKHNpbGx5X3NvdXJjZSkNCmBgYA0KDQpPa2F5LCB0aGV5IGFwcGVhciB0byBiZSB0cnVlIGR1cGxpY2F0ZXMsIG5vIGNvbmZsaWN0aW5nIGNvbHVtbnMuIFJlbW92ZSBkdXBsaWNhdGUgcm93cy4NCmBgYHtyfQ0KbWV0YWRhdGEgPC0gbWV0YWRhdGEgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3Qoc2lsbHlfc291cmNlLCAua2VlcF9hbGwgPSBUUlVFKQ0KYGBgDQoNCiMjIEpvaW4gTWV0YWRhdGENCg0KSm9pbiBgbWV0YWRhdGFgIHRvIGVhY2ggc2lsbHkgYnkgYFNpbGx5U291cmNlYC4NCmBgYHtyfQ0KeCA8LSBzYXBwbHkoc2lsbHl2ZWMsIGZ1bmN0aW9uKHNpbGx5KSB7DQogIG15LnNpbGx5IDwtIGdldChwYXN0ZTAoc2lsbHksICIuZ2NsIikpDQogIG15LnNpbGx5IDwtIG15LnNpbGx5ICU+JSBkcGx5cjo6bGVmdF9qb2luKA0KICAgIHkgPSBtZXRhZGF0YSAlPiUgZHBseXI6OnNlbGVjdCgtZ2Vub3R5cGVkKSwNCiAgICBieSA9IGRwbHlyOjpqb2luX2J5KFNpbGx5U291cmNlID09IHNpbGx5X3NvdXJjZSkNCiAgKQ0KICBhc3NpZ24oeCA9IHBhc3RlMChzaWxseSwgIi5nY2wiKSwNCiAgICAgICAgIHZhbHVlID0gbXkuc2lsbHksDQogICAgICAgICBwb3MgPSAxKQ0KfSkNCnJtKHgpICAjIHN1cHByZXNzZXMgb3V0cHV0DQpgYGANCg0KIyBGaWx0ZXIgUUMgQ3Jvc3NjaGVjayBQbGF0ZXMNCg0KQ3JlYXRlIGEgdmVjdG9yIG9mIHBsYXRlIElEcyB0byB0b3NzLCBwZW5kaW5nIFFDIGNyb3NzY2hlY2tzLg0KYGBge3J9DQpwbGF0ZV9pZHNfcWN4Y2hlY2sgPC0gYyg2NDQ2MSwgNjQ0NjIsIDY0NDgwLCA2NDQ4MiwgNjQ0ODMsIDY0NDg0LCA2NDQ4NSwgNjQ0ODYsIDY0NTAyLCA2NDUwMywgNjQ1MDksIDY0NTExLCA2NDUxMiwgNjQ1MTMsIDY0NTE0LCA2NDUxNSwgNjQ1NDIsIDY0NTQzLCA2NDU4MiwgNTYzNjIsIDU2MzYzKSAlPiUgYXMuY2hhcmFjdGVyKCkNCmBgYA0KDQpHZXQgc2FtcGxlIHNpemUgYnkgc2lsbHkuDQpgYGB7cn0NCigNCiAgc2FtcGxlX3NpemVfcWN4Y2hlY2sgPC0gZHBseXI6OnRpYmJsZShzaWxseSA9IHNpbGx5dmVjKSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKGdlbm90eXBlZCA9IEdDTHI6OnNpbGx5X24oc2lsbHl2ZWMgPSBzaWxseXZlYykgJT4lIGRwbHlyOjpwdWxsKG4pKQ0KKQ0KYGBgDQoNClJlbW92ZSBhbnkgZmlzaCBvbiB0aGUgcGxhdGVzIHBlbmRpbmcgUUMgY3Jvc3NjaGVja3MuDQpgYGB7cn0NCnggPC0gc2FwcGx5KHNpbGx5dmVjLCBmdW5jdGlvbihzaWxseSkgew0KICBteS5zaWxseSA8LSBnZXQocGFzdGUwKHNpbGx5LCAiLmdjbCIpKQ0KICBteS5zaWxseSA8LSBteS5zaWxseSAlPiUgZHBseXI6OmZpbHRlcighc3RyaW5ncjo6c3RyX2RldGVjdChzdHJpbmcgPSBQTEFURV9JRCwgcGF0dGVybiA9IHN0cmluZ3I6OnN0cl9jKHBsYXRlX2lkc19xY3hjaGVjaywgY29sbGFwc2UgPSAifCIpKSkNCiAgYXNzaWduKHggPSBwYXN0ZTAoc2lsbHksICIuZ2NsIiksDQogICAgICAgICB2YWx1ZSA9IG15LnNpbGx5LA0KICAgICAgICAgcG9zID0gMSkNCn0pDQpybSh4KSAgIyBzdXBwcmVzc2VzIG91dHB1dA0KYGBgDQoNCkhvdyBtYW55IGZpc2ggd2VyZSByZW1vdmVkIGJ5IHNpbGx5Pw0KYGBge3J9DQooDQogIHNhbXBsZV9zaXplX3FjeGNoZWNrIDwtIHNhbXBsZV9zaXplX3FjeGNoZWNrICU+JQ0KICAgIGRwbHlyOjptdXRhdGUocWN4Y2hlY2sgPSBnZW5vdHlwZWQgLSBHQ0xyOjpzaWxseV9uKHNpbGx5dmVjID0gc2lsbHl2ZWMpICU+JSBkcGx5cjo6cHVsbChuKSkNCikNCmBgYA0KDQpIb3cgbWFueSB0b3RhbCBzYW1wbGVzPw0KYGBge3J9DQpzdW0oc2FtcGxlX3NpemVfcWN4Y2hlY2skcWN4Y2hlY2spDQpgYGANCg0KT2theSwgdGhhdCBsb29rcyBhYm91dCByaWdodCBmb3IgMjEgcGxhdGVzLiBNdXN0IGhhdmUgYmVlbiBzb21lIHBhcnRpYWwgcGxhdGVzLg0KDQojIEdlbm90eXBlIFFBDQoNClN0YW5kYXJkIGRhdGEgUUEgKEdULXNlcSwgbm8gYGNvblNjb3JlYCk6DQoNCiAgKiBSZW1vdmUgZmlzaCBtaXNzaW5nIGdlbm90eXBlcyAoKio8ODAlKiogbG9jaSkNCiAgKiBSZW1vdmUgY29udGFtaW5hdGlvbiAoaGV0ZXJvenlnb3NpdHkgb3V0bGllcnMgKiorLy0gMy41KiogbW9kaWZpZWQgei1zY29yZSkNCiAgKiBSZW1vdmUgZHVwbGljYXRlcyAoKio+OTUlKiogbG9jaSBjb25jb3JkYW5jZSwgcmVtb3ZlICoqYm90aCoqIGR1cGxpY2F0ZXMpDQoNCmBgYHtyfQ0KKA0KICBzYW1wbGVfc2l6ZV9xYSA8LSBkcGx5cjo6dGliYmxlKHNpbGx5ID0gc2lsbHl2ZWMpICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoZ2Vub3R5cGVkID0gR0NMcjo6c2lsbHlfbihzaWxseXZlYyA9IHNpbGx5dmVjKSAlPiUgZHBseXI6OnB1bGwobikpDQopDQpgYGANCg0KIyMgTWlzc2luZyBMb2NpICg8ODAlKQ0KDQpSZW1vdmUgZmlzaCB3aXRoIDw4MCUgbG9jaSBnZW5vdHlwZWQuDQpgYGB7cn0NCm1pc3NfbG9jaSA8LQ0KICBHQ0xyOjpyZW1vdmVfaW5kX21pc3NfbG9jaShzaWxseXZlYyA9IHNpbGx5dmVjLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9wb3J0aW9uID0gMC44KQ0KDQojIG1pc3NfbG9jaSRJRHNfUmVtb3ZlZA0KDQpHQ0xyOjpzYXZlX29iamVjdHMob2JqZWN0cyA9ICJtaXNzX2xvY2kiLCBwYXRoID0gICIuLi9vYmplY3RzIiwgcmRzID0gVFJVRSkNCg0KKA0KICBzYW1wbGVfc2l6ZV9xYSA8LSBzYW1wbGVfc2l6ZV9xYSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKG1pc3NpbmcgPSBnZW5vdHlwZWQgLSBHQ0xyOjpzaWxseV9uKHNpbGx5dmVjID0gc2lsbHl2ZWMpICU+JSBkcGx5cjo6cHVsbChuKSkNCikNCmBgYA0KDQpPdmVyYWxsIGByIGZvcm1hdChzdW0oc2FtcGxlX3NpemVfcWEkbWlzc2luZyksIGJpZy5tYXJrID0gIiwiKWAgc2FtcGxlcyBkcm9wcGVkIGR1ZSB0byBwb29yIHF1YWxpdHkgRE5BLiANCg0KV2hhdCB3YXMgZ2Vub3R5cGluZyBzdWNjZXNzIGJ5IHNwYXduaW5nIHN0YXRlPw0KYGBge3J9DQptaXNzX2xvY2lfdGliIDwtIHRpYmJsZTo6ZW5mcmFtZShtaXNzX2xvY2kkSURzX1JlbW92ZWQsIG5hbWUgPSAic2lsbHkiLCB2YWx1ZSA9ICJmaXNoX2lkIikgJT4lIA0KICB0aWR5cjo6dW5uZXN0X2xvbmdlcihmaXNoX2lkKSAlPiUgDQogIGRwbHlyOjpsZWZ0X2pvaW4oeSA9IG1ldGFkYXRhLCBieSA9IGRwbHlyOjpqb2luX2J5KHNpbGx5LCBmaXNoX2lkKSkNCg0KbWV0YWRhdGEgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGdlbm90eXBlZCkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGRyb3BfbWlzcyA9IHNpbGx5X3NvdXJjZSAlaW4lIG1pc3NfbG9jaV90aWIkc2lsbHlfc291cmNlKSAlPiUgDQogIGRwbHlyOjpjb3VudChkcm9wX21pc3MsIHNwYXduaW5nX3N0YXRlKSAlPiUgDQogIHRpZHlyOjpwaXZvdF93aWRlcihuYW1lc19mcm9tID0gZHJvcF9taXNzLCB2YWx1ZXNfZnJvbSA9IG4pICU+JSANCiAgZHBseXI6OnJlbmFtZShuX2Ryb3AgPSBgVFJVRWAsIG5fa2VlcCA9IGBGQUxTRWApICU+JSANCiAgZHBseXI6Om11dGF0ZShuID0gbl9rZWVwICsgbl9kcm9wLA0KICAgICAgICAgICAgICAgIHBfa2VlcCA9IG5fa2VlcCAvIG4sDQogICAgICAgICAgICAgICAgc3Bhd25pbmdfc3RhdGUgPSBmYWN0b3Ioc3Bhd25pbmdfc3RhdGUsIGxldmVscyA9IGMoIkFsaXZlIiwgIlBpbmsgR2lsbCIsICJHcmV5IEdpbGwiLCAiUm90dGluZyIpKSkgJT4lIA0KICBkcGx5cjo6YXJyYW5nZShzcGF3bmluZ19zdGF0ZSkNCmBgYA0KDQpBcyBleHBlY3RlZCwgZ2Vub3R5cGluZyBzdWNjZXNzIHdhcyBtdWNoIGxvd2VyIGZvciByb3R0aW5nIGZpc2guDQoNCiMjIENvbnRhbWluYXRpb24gKEhldGVyb3p5Z29zaXR5ID4gKy8tIDMuNSBtb2RpZmllZCBaLXNjb3JlKQ0KDQpSZW1vdmUgY29udGFtaW5hdGVkIHNhbXBsZXMgcHJpb3IgdG8gY2hlY2tpbmcgZm9yIGR1cGxpY2F0ZXMhDQoNCkluZGl2aWR1YWwgaGV0ZXJvenlnb3NpdHkgb3V0bGllcnMgaXMgb3VyIGJlc3QgbWV0cmljIGZvciByZW1vdmluZyBjb250YW1pbmF0ZWQgc2FtcGxlcyBpbiB0aGUgYWJzZW5jZSBvZiBzb21ldGhpbmcgbW9yZSBzb3BoaXN0aWNhdGVkIGxpa2UgKmNvblNjb3JlKiBmcm9tIFtHVHNjb3JlXShodHRwczovL2dpdGh1Yi5jb20vZ2ptY2tpbm5leS9HVHNjb3JlP3RhYj1yZWFkbWUtb3YtZmlsZSNzYW1wbGUtc3VtbWFyaWVzKS4gSW4gcHJldmlvdXMgYW5hbHlzZXMsIGluY2x1ZGluZyBbU2hlZGQgZXQgYWwuIDIwMjJdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTExL2V2YS4xMzM1NiksIHdlJ3ZlIHVzZWQgdGhlIHN0YW5kYXJkIDEuNSBJUVIgb3V0bGllciBkZXRlY3Rpb24gbWV0aG9kIHRvIHJlbW92ZSBleGNlc3NpdmVseSBoZXRlcm96eWdvdXMgaW5kaXZpZHVhbHMuIEhvd2V2ZXIsIHRoZSAxLjUgSVFSIG1ldGhvZCBhc3N1bWVzIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbiwgYnV0IGhlYXJ0IHNhbXBsZSBoZXRlcm96eWdvc2l0aWVzIHRlbmQgdG8gYmUgc2tld2VkIHJpZ2h0IGR1ZSB0byBjb250YW1pbmF0aW9uLiBBcyBvZiAyMDI0LTAyLTA4LCB3ZSBkZWNpZGVkIHRvIHBpdm90IHRvIHVzaW5nIFttb2RpZmllZCBaLXNjb3Jlc10oaHR0cHM6Ly93d3cuaXRsLm5pc3QuZ292L2Rpdjg5OC9oYW5kYm9vay9lZGEvc2VjdGlvbjMvZWRhMzVoLmh0bSkgd2l0aCBjdXRvZmZzIG9mICsvLSAzLjUgYXMgcmVjb21tZW5kZWQgYnkgSWdsZXdpY3ogYW5kIEhvYWdsaW4gW14xXS4gVGhlIG1vZGlmaWVkIFotc2NvcmUgdXNlcyB0aGUgbWVkaWFuIGFuZCBtZWRpYW4gYWJzb2x1dGUgZGV2aWF0aW9uIChNQUQpIGluc3RlYWQgb2YgdGhlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiwgYW5kIGlzIHRodXMgbW9yZSByb2J1c3QgdG8gb3V0bGllcnMgYW5kIGFzeW1tZXRyaWNhbCBkaXN0cmlidXRpb25zLg0KDQpbXjFdOiBCb3JpcyBJZ2xld2ljeiBhbmQgRGF2aWQgSG9hZ2xpbiAoMTk5MyksICJWb2x1bWUgMTY6IEhvdyB0byBEZXRlY3QgYW5kIEhhbmRsZSBPdXRsaWVycyIsIFRoZSBBU1FDIEJhc2ljIFJlZmVyZW5jZXMgaW4gUXVhbGl0eSBDb250cm9sOiBTdGF0aXN0aWNhbCBUZWNobmlxdWVzLCBFZHdhcmQgRi4gTXlreXRrYSwgUGguRC4sIEVkaXRvci4gDQoNClNvdXJjZSBmdW5jdGlvbiBhbmQgY2FsY3VsYXRlIGluZGl2aWR1YWwgaGV0ZXJvenlnb3NpdGllcyBmcm9tIGBBSFJQLVBXUy1QaW5rLVNhbG1vbi1QZWRpZ3JlZXNgIEdpdEh1YiByZXBvLg0KYGBge3J9DQpzb3VyY2UoIn4vR2l0SHViX3JlcG9zL0FIUlAtUFdTLVBpbmstU2FsbW9uLVBlZGlncmVlcy9jb2RlL2Z1bmN0aW9ucy9jYWxjX2luZF9oZXQuUiIpDQoNCigNCiAgaW5kX2hldCA8LQ0KICAgIGNhbGNfaW5kX2hldCgNCiAgICAgIHNpbGx5dmVjID0gc2lsbHl2ZWMsDQogICAgICBsb2NpID0gbG9jaTI1MSwNCiAgICAgIG5jb3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gNA0KICAgICkNCikNCmBgYA0KDQpDYWxjdWxhdGUgKy8tIDMuNSBtb2RpZmllZCB6LXNjb3JlIGN1dG9mZnMgYnkgKnN0cmVhbSouIA0KKipOT1RFKiogdGhpcyBtZWFucyB0aGF0IHRoZSBleGFjdCBoZXRlcm96eWdvc2l0eSBjdXRvZmYgd2lsbCB2YXJ5IHNsaWdodGx5IGJ5IHN0cmVhbS4gRm9yIFBXUyBQaW5rIFNhbG1vbiwgSSBkaWQgaXQgc2VwYXJhdGVseSBieSBzaWxseSwgYnV0IHNvbWUgb2Ygb3VyIHNhbXBsZSBzaXplcyBhcmUgcHJldHR5IHNtYWxsIGhlcmUgYW5kIHdlIGhhdmUgdGhlIHNlcGFyYXRlICJ0YWciIHNpbGx5cy4gSSdtIG9wdGluZyB0byBkbyB0aGlzIGJ5IHN0cmVhbSB0byBhbGxvdyBmb3Igc3VidGxlIGNoYW5nZXMgaW4gYWxsZWxlIGZyZXF1ZW5jeSBwZXIgc3RyZWFtLg0KYGBge3J9DQojIEZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBtZWRpYW4gYWJzb2x1dGUgZGV2aWF0aW9uIChNQUQpDQptYWQgPC0gZnVuY3Rpb24oeCkgew0KICBtZWRpYW4oYWJzKHggLSBtZWRpYW4oeCkpKQ0KfQ0KDQojIENhbGN1bGF0ZSB0aGUgbW9kaWZpZWQgei1zY29yZSBmb3IgZWFjaCBpbmRpdmlkdWFsDQooDQogIGluZF9oZXQgPC0gaW5kX2hldCAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKA0KICAgICAgeWVhciA9IDIwMDAgKyBhcy5udW1lcmljKHN0cmluZ3I6OnN0cl9zdWIoDQogICAgICAgIHN0cmluZyA9IHNpbGx5LA0KICAgICAgICBzdGFydCA9IC0yLA0KICAgICAgICBlbmQgPSAtMQ0KICAgICAgKSksDQogICAgICBzdHJlYW0gPSBkcGx5cjo6Y2FzZV93aGVuKA0KICAgICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KHN0cmluZyA9IHNpbGx5LCBwYXR0ZXJuID0gIkFETSIpIH4gIkFkbWlyYWx0eSIsDQogICAgICAgIHN0cmluZ3I6OnN0cl9kZXRlY3Qoc3RyaW5nID0gc2lsbHksIHBhdHRlcm4gPSAiRklTSCIpIH4gIkZpc2giLA0KICAgICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KHN0cmluZyA9IHNpbGx5LCBwYXR0ZXJuID0gIlBST1MiKSB+ICJQcm9zcGVjdCIsDQogICAgICAgIHN0cmluZ3I6OnN0cl9kZXRlY3Qoc3RyaW5nID0gc2lsbHksIHBhdHRlcm4gPSAiU0FXIikgfiAiU2F3bWlsbCIsDQogICAgICAgIFRSVUUgfiAibWlzdGFrZXNfd2VyZV9tYWRlIg0KICAgICAgKQ0KICAgICkgJT4lDQogICAgZHBseXI6Omdyb3VwX2J5KHN0cmVhbSkgJT4lICAjIGN1dG9mZnMgYXJlIHNwZWNpZmljIHRvIGVhY2ggc3RyZWFtIQ0KICAgIGRwbHlyOjptdXRhdGUoDQogICAgICBtb2RpZmllZF96X3Njb3JlID0gMC42NzQ1ICogKGhldCAtIG1lZGlhbihoZXQpKSAvIG1hZChoZXQpLA0KICAgICAgb3V0bGllciA9IGRwbHlyOjpjYXNlX3doZW4oYWJzKG1vZGlmaWVkX3pfc2NvcmUpID4gMy41IH4gVFJVRSwgVFJVRSB+IEZBTFNFKQ0KICAgICkgJT4lDQogICAgZHBseXI6OnVuZ3JvdXAoKQ0KKQ0KICANCkdDTHI6OnNhdmVfb2JqZWN0cygiaW5kX2hldCIsIHBhdGggPSAib2JqZWN0cyIsIHJkcyA9IFRSVUUpDQpgYGANCg0KUGxvdCBkaXN0cmlidXRpb24gb2YgaGV0ZXJvenlnb3NpdHkgYW5kIHNob3cgb3V0bGllcnMuDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTEwfQ0KaW5kX2hldCAlPiUgDQogIGdncGxvdDI6OmdncGxvdChnZ3Bsb3QyOjphZXMoeCA9IGhldCwgZmlsbCA9IG91dGxpZXIpKSArDQogIGdncGxvdDI6Omdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSAvIGxlbmd0aChsb2NpMjUxKSkgKw0KICBnZ3Bsb3QyOjpmYWNldF9ncmlkKHJvd3MgPSBnZ3Bsb3QyOjp2YXJzKHN0cmVhbSksIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGdncGxvdDI6OnhsaW0oMCwgMSkgKw0KICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIk91dGxpZXIiLCB2YWx1ZXMgPSBjKCJUUlVFIiA9ICJibGFjayIsICJGQUxTRSIgPSAiZ3JleTYwIikpICsNCiAgZ2dwbG90Mjo6eGxhYigiSW5kaXZpZHVhbCBIZXRlcm96eWdvc2l0eSIpICsNCiAgZ2dwbG90Mjo6eWxhYigiRnJlcXVlbmN5IikgKw0KICBnZ3Bsb3QyOjpnZ3RpdGxlKCJJbmRpdmlkdWFsIEhldGVyb3p5Z29zaXR5IC0gQnkgU3RyZWFtIikgKw0KICBnZ3Bsb3QyOjp0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgDQpgYGANCg0KUmVtb3ZlIG91dGxpZXJzLg0KYGBge3J9DQpvdXRwdXQgPC0gbGFwcGx5KHNpbGx5dmVjLCBmdW5jdGlvbih4KSB7DQogIEdDTHI6OnJlbW92ZV9pZHMoDQogICAgc2lsbHkgPSB4LA0KICAgIElEcyA9IGluZF9oZXQgJT4lIGRwbHlyOjpmaWx0ZXIob3V0bGllciwgc2lsbHkgPT0geCkgJT4lIGRwbHlyOjpwdWxsKGZpc2hfaWQpDQogICkNCn0pDQoNCm1lc3NhZ2UocGFzdGUwKCJBIHRvdGFsIG9mICIsIGluZF9oZXQgJT4lIGRwbHlyOjpmaWx0ZXIob3V0bGllcikgJT4lIG5yb3coKSwgIiBpbmRpdmlkdWFscyB3ZXJlIHJlbW92ZWQgZnJvbSBzaWxseXMgaW4gc2lsbHl2ZWMuIikpDQpgYGANCg0KTm90IHRvbyBtYW55IGNvbnRhbWluYXRlZCBzYW1wbGVzISBNb3N0IGxpa2VseSBjdWxsZWQgZHVyaW5nIG1pc3NpbmcgbG9jaS4NCmBgYHtyfQ0KKA0KICBzYW1wbGVfc2l6ZV9xYSA8LSBzYW1wbGVfc2l6ZV9xYSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKGhldGVyb3p5Z29zaXR5ID0gZ2Vub3R5cGVkIC0gbWlzc2luZyAtIEdDTHI6OnNpbGx5X24oc2lsbHl2ZWMgPSBzaWxseXZlYykgJT4lIGRwbHlyOjpwdWxsKG4pKQ0KKQ0KYGBgDQoNCk92ZXJhbGwgYHIgZm9ybWF0KHN1bShzYW1wbGVfc2l6ZV9xYSRoZXRlcm96eWdvc2l0eSksIGJpZy5tYXJrID0gIiwiKWAgc2FtcGxlcyBkcm9wcGVkIGR1ZSB0byBzYW1wbGUgY29udGFtaW5hdGlvbi4gDQoNCiMjIER1cGxpY2F0ZSAoPj05NSUpDQoNCklkZW50aWZ5IHBvdGVudGlhbCBkdXBsaWNhdGUgZ2Vub3R5cGVzICg+PTk1JSBsb2NpKS4NCmBgYHtyfQ0KZHVwbGljYXRlX2NoZWNrXzk1IDwtDQogIEdDTHI6OmR1cGNoZWNrX3dpdGhpbl9zaWxseSgNCiAgICBzaWxseXZlYyA9IHNpbGx5dmVjLA0KICAgIG1pbnByb3BvcnRpb24gPSAwLjk1LA0KICAgIG1pbm5vbm1pc3NpbmcgPSAwLjYsDQogICAgbmNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSA0DQogICkNCg0KR0NMcjo6c2F2ZV9vYmplY3RzKCJkdXBsaWNhdGVfY2hlY2tfOTUiLCBwYXRoID0gIm9iamVjdHMiLCByZHMgPSBUUlVFKQ0KYGBgDQoNCkhvdyBtYW55IGR1cGxpY2F0ZSBwYWlycyBieSBzaWxseT8NCmBgYHtyfQ0KZHVwbGljYXRlX2NoZWNrXzk1ICU+JSANCiAgZHBseXI6OmNvdW50KHNpbGx5KSAlPiUgDQogIGRwbHlyOjphcnJhbmdlKGRwbHlyOjpkZXNjKG4pKQ0KYGBgDQoNClJlbW92ZSAqKmJvdGgqKiBkdXBsaWNhdGVzISBBcyBvcHBvc2VkIHRvIEdTSSB3b3JrLCB3aGVyZSB3ZSB3YW50IHRvIGtlZXAgaW5kaXZpZHVhbHMgYnV0IGFyZW7igJl0IHR5cGljYWxseSB3b3JyaWVkIGFib3V0IHBhaXJlZCBkYXRhLCBoZXJlIHdlIHdhbnQgdG8gcmVtb3ZlIGJvdGggaW5kaXZpZHVhbHMgYXMgdGhlIHBhaXJlZCBkYXRhIGludGVncml0eSAoaW5jbHVkaW5nIG90b2xpdGggcmVhZHMhKSBpcyBsb3N0Lg0KYGBge3J9DQpkdXBsaWNhdGVzX3JlbW92ZWQgPC0NCiAgR0NMcjo6cmVtb3ZlX2R1cHMoZHVwY2hlY2sgPSBkdXBsaWNhdGVfY2hlY2tfOTUsIHJlbW92ZV9ib3RoID0gVFJVRSkNCg0KR0NMcjo6c2F2ZV9vYmplY3RzKCJkdXBsaWNhdGVzX3JlbW92ZWQiLCBwYXRoID0gIm9iamVjdHMiLCByZHMgPSBUUlVFKQ0KDQooDQogIHNhbXBsZV9zaXplX3FhIDwtIHNhbXBsZV9zaXplX3FhICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoDQogICAgICBkdXBsaWNhdGUgPSBnZW5vdHlwZWQgLSBtaXNzaW5nIC0gaGV0ZXJvenlnb3NpdHkgLSBHQ0xyOjpzaWxseV9uKHNpbGx5dmVjID0gc2lsbHl2ZWMpICU+JSBkcGx5cjo6cHVsbChuKQ0KICAgICkNCikNCmBgYA0KDQpPdmVyYWxsIGByIGZvcm1hdChzdW0oc2FtcGxlX3NpemVfcWEkZHVwbGljYXRlKSwgYmlnLm1hcmsgPSAiLCIpYCBzYW1wbGVzIGRyb3BwZWQgZHVlIHRvIGR1cGxpY2F0ZSBnZW5vdHlwZXMuIA0KDQojIyBGaW5hbA0KDQpGaW5hbCwgcG9zdC1RQSBzYW1wbGUgc2l6ZXMgYnkgc2lsbHkuDQpgYGB7cn0NCigNCiAgc2FtcGxlX3NpemVfcWEgPC0gc2FtcGxlX3NpemVfcWEgJT4lDQogICAgZHBseXI6Om11dGF0ZSgNCiAgICAgIGZpbmFsID0gIEdDTHI6OnNpbGx5X24oc2lsbHl2ZWMgPSBzaWxseXZlYykgJT4lIGRwbHlyOjpwdWxsKG4pDQogICAgKQ0KKQ0KDQpHQ0xyOjpzYXZlX29iamVjdHMoInNhbXBsZV9zaXplX3FhIiwgcGF0aCA9ICJvYmplY3RzIiwgcmRzID0gVFJVRSkNCg0KZnM6OmRpcl9jcmVhdGUoIm91dHB1dCIpDQpyZWFkcjo6d3JpdGVfY3N2KHggPSBzYW1wbGVfc2l6ZV9xYSwgZmlsZSA9ICJvdXRwdXQvc2FtcGxlX3NpemVfcWEuY3N2IikNCmBgYA0KDQojIFNhdmUNCg0KU2F2ZSBmaW5hbCBnZW5vdHlwZXMgYW5kIHVwZGF0ZSBtZXRhZGF0YS4NCg0KIyMgR2Vub3R5cGVzDQoNClNhdmUgcG9zdC1RQSBnZW5vdHlwZXMgd2l0aCBqb2luZWQgbWV0YWRhdGEuDQpgYGB7cn0NCmZzOjpkaXJfY3JlYXRlKCJkYXRhL2dlbm90eXBlcy9wb3N0UUEiKQ0KR0NMcjo6c2F2ZV9zaWxseXMoc2lsbHl2ZWMgPSBzaWxseXZlYywgcGF0aCA9ICJkYXRhL2dlbm90eXBlcy9wb3N0UUEiLCByZHMgPSBUUlVFKQ0KYGBgDQoNCiMjIE1ldGFkYXRhDQoNCmFkZCBgcGFzc19RQWANCmBgYHtyfQ0KcG9zdFFBX3NpbGx5X3NvdXJjZSA8LSBzYXBwbHkoc2lsbHl2ZWMsIGZ1bmN0aW9uKHNpbGx5KSB7Z2V0KHBhc3RlMChzaWxseSwgIi5nY2wiKSkkU2lsbHlTb3VyY2V9KSAlPiUgdW5saXN0KCkNCg0KbWV0YWRhdGEgPC0gbWV0YWRhdGEgJT4lIA0KICBkcGx5cjo6bXV0YXRlKHBhc3NfUUEgPSBzaWxseV9zb3VyY2UgJWluJSBwb3N0UUFfc2lsbHlfc291cmNlKQ0KYGBgDQoNCkNvbmZpcm0uDQpgYGB7cn0NCm1ldGFkYXRhICU+JSANCiAgZHBseXI6OmNvdW50KGdlbm90eXBlZCwgcGFzc19RQSkNCmBgYA0KDQoqKk5PVEUqKiB0aGF0IGBnZW5vdHlwZWRgIGluIGBzYW1wbGVfc2l6ZV9xYWAgZG9lc24ndCBtYXRjaCBgZ2Vub3R5cGVkYCBpbiBgbWV0YWRhdGFgIGJlY2F1c2UgSSBwcmUtZmlsdGVyZWQgb3V0IFFDIENyb3NzY2hlY2sgcGxhdGVzLg0KYGBge3J9DQpzYW1wbGVfc2l6ZV9xYSAlPiUgDQogIGphbml0b3I6OmFkb3JuX3RvdGFscygpICU+JSANCiAgZHBseXI6OmZpbHRlcihzaWxseSA9PSAiVG90YWwiKQ0KYGBgDQoNCiMgUUEgQ29uY2x1c2lvbnMNCg0KT3ZlcmFsbCBgciBmb3JtYXQoc3VtKG1ldGFkYXRhJGdlbm90eXBlZCksIGJpZy5tYXJrID0gIiwiKWAgdG90YWwgcGVkaWdyZWUgc2FtcGxlcyB3ZXJlIGdlbm90eXBlZDsgYHIgZm9ybWF0KHN1bShtZXRhZGF0YSRnZW5vdHlwZWQpIC0gc3VtKHNhbXBsZV9zaXplX3FhJGdlbm90eXBlZCksIGJpZy5tYXJrID0gIiwiKWAgd2VyZSByZW1vdmVkIGR1ZSB0byBwZW5kaW5nIFFDIENyb3NzY2hlY2tzOyBgciBmb3JtYXQoc3VtKHNhbXBsZV9zaXplX3FhJG1pc3NpbmcpLCBiaWcubWFyayA9ICIsIilgIHdlcmUgcmVtb3ZlZCBkdWUgdG8gcG9vciBxdWFsaXR5IEROQSAobWlzc2luZyA+PSAyMCUgbG9jaSk7IGByIGZvcm1hdChzdW0oc2FtcGxlX3NpemVfcWEkaGV0ZXJvenlnb3NpdHkpLCBiaWcubWFyayA9ICIsIilgIHdlcmUgcmVtb3ZlZCBkdWUgdG8gY29udGFtaW5hdGVkIEROQSAoaGV0ZXJvenlnb3NpdHkgPj0gMy41IG1vZGlmaWVkIHotc2NvcmUpOyBhbmQgYHIgZm9ybWF0KHN1bShzYW1wbGVfc2l6ZV9xYSRkdXBsaWNhdGUpLCBiaWcubWFyayA9ICIsIilgIHdlcmUgcmVtb3ZlZCBkdWUgdG8gZHVwbGljYXRlIGdlbm90eXBlcyAoPj0gOTUlIGxvY2kpLCBsZWF2aW5nIGEgdG90YWwgb2YgKipgciBmb3JtYXQoc3VtKHNhbXBsZV9zaXplX3FhJGZpbmFsKSwgYmlnLm1hcmsgPSAiLCIpYCoqIGluIHRoZSBmaW5hbCBkYXRhc2V0Lg0KDQojIEFkYXB0aXZlIExvY2kgR2Vub3R5cGVzDQoNCkNyZWF0ZSBhIHNpbmdsZSBvYmplY3Qgd2l0aCBqdXN0IHRoZSBtZXRhZGF0YSBhbmQgYWRhcHRpdmUgbG9jaSBnZW5vdHlwZXMuDQpgYGB7cn0NCigNCiAgY2h1bV9hZGFwdGl2ZV9nZW5vIDwtIHNhcHBseShzaWxseXZlYywgZnVuY3Rpb24oc2lsbHkpIHsNCiAgICBnZXQocGFzdGUwKHNpbGx5LCAiLmdjbCIpKQ0KICB9LCBzaW1wbGlmeSA9IEZBTFNFKSAlPiUNCiAgICBkcGx5cjo6YmluZF9yb3dzKCkgJT4lDQogICAgZHBseXI6OnNlbGVjdCgNCiAgICAgIHNpbGx5X3NvdXJjZSA9IFNpbGx5U291cmNlLA0KICAgICAgcGxhdGVfaWQgPSBQTEFURV9JRCwNCiAgICAgIHN0cmVhbTpzd19hZ2UsDQogICAgICBkcGx5cjo6Y29udGFpbnMoIk9rZVYyIikNCiAgICApDQopDQpgYGANCg0KIyMgQ2FsY3VsYXRlIEFsbGVsZSBGcmVxdWVuY2llcw0KDQpDYWxjdWxhdGUgdGhlIGFsbGVsZSBjb3VudHMgZm9yIGVhY2ggbG9jdXMgYnkgc3RyZWFtLCB5ZWFyLCBhbmQgRE9ZLg0KQWRhcHRlZCBmcm9tIFBhdCBCYXJyeSdzIGNvZGUgaW4gYEFIUlBfQ2h1bUFkYXB0aXZlLmh0bWxgDQpgYGB7cn0NCmxvY2lfYWRhcHRpdmUgPC0gZ3JlcChwYXR0ZXJuID0gIk9rZVYyIiwgeCA9IGxvY2kyNTEsIHZhbHVlID0gVFJVRSkNCg0KIyB0ZXN0aW5nDQojIGNodW1fYWRhcHRpdmVfZ2VubyAlPiUNCiMgICBkcGx5cjo6c2VsZWN0KHNpbGx5X3NvdXJjZSwgc3RyZWFtLCB5ZWFyLCBET1ksIHRpZHlzZWxlY3Q6OnN0YXJ0c193aXRoKGxvY2lfYWRhcHRpdmVbMV0pKSAlPiUNCiMgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGNvbHMgPSB0aWR5c2VsZWN0OjpzdGFydHNfd2l0aChsb2NpX2FkYXB0aXZlWzFdKSwgbmFtZXNfdG8gPSAibG9jdXMiLCB2YWx1ZXNfdG8gPSAiYWxsZWxlIikgJT4lDQojICAgZHBseXI6Om11dGF0ZShhbGxlbGUgPSBmYWN0b3IoYWxsZWxlKSwNCiMgICAgICAgICAgICAgICAgIGFsbGVsZSA9IGFzLm51bWVyaWMoYWxsZWxlKSkgJT4lICAjIG1ha2UgZmFjdG9yIGFuZCBjb252ZXJ0IHRvIG51bWVyaWMNCiMgICBkcGx5cjo6Y291bnQoc3RyZWFtLCB5ZWFyLCBET1ksIGFsbGVsZSkgJT4lDQojICAgZHBseXI6Om11dGF0ZShsb2N1cyA9IGxvY2lfYWRhcHRpdmVbMV0pDQoNCiMgY2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgZWFjaCBhbGxlbGUgb2JzZXJ2ZWQgcGVyIHN0cmVhbSwgeWVhciwgZGF5DQooY2h1bV9hZGFwdGl2ZV9hbGxlbGVfY291bnRzIDwtIGxhcHBseShsb2NpX2FkYXB0aXZlLCBmdW5jdGlvbihsb2N1cykgew0KICBjaHVtX2FkYXB0aXZlX2dlbm8gJT4lDQogICAgZHBseXI6OnNlbGVjdChzaWxseV9zb3VyY2UsDQogICAgICAgICAgICAgICAgICBzdHJlYW0sDQogICAgICAgICAgICAgICAgICB5ZWFyLA0KICAgICAgICAgICAgICAgICAgRE9ZLA0KICAgICAgICAgICAgICAgICAgdGlkeXNlbGVjdDo6c3RhcnRzX3dpdGgobG9jdXMpKSAlPiUNCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKA0KICAgICAgY29scyA9IHRpZHlzZWxlY3Q6OnN0YXJ0c193aXRoKGxvY3VzKSwNCiAgICAgIG5hbWVzX3RvID0gImxvY3VzIiwNCiAgICAgIHZhbHVlc190byA9ICJhbGxlbGUiDQogICAgKSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKGFsbGVsZSA9IGZhY3RvcihhbGxlbGUpLA0KICAgICAgICAgICAgICAgIGFsbGVsZSA9IGFzLm51bWVyaWMoYWxsZWxlKSkgJT4lICAjIG1ha2UgZmFjdG9yIGFuZCBjb252ZXJ0IHRvIG51bWVyaWMNCiAgICBkcGx5cjo6Y291bnQoc3RyZWFtLCB5ZWFyLCBET1ksIGFsbGVsZSkgJT4lDQogICAgZHBseXI6Om11dGF0ZShsb2N1cyA9IGxvY3VzKQ0KfSkgJT4lDQogICAgZHBseXI6OmJpbmRfcm93cygpKQ0KYGBgDQoNCkNvbnZlcnQgdG8gYWxlbGxlIGZyZXF1ZW5jaWVzLg0KYGBge3J9DQooDQogIGNodW1fYWRhcHRpdmVfYWxsZWxlX2ZyZXEgPC0gY2h1bV9hZGFwdGl2ZV9hbGxlbGVfY291bnRzICU+JQ0KICAgIHRpZHlyOjpwaXZvdF93aWRlcihuYW1lc19mcm9tID0gYWxsZWxlLCB2YWx1ZXNfZnJvbSA9IG4pICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoLWBOQWApICU+JSAgIyBkcm9wIG5vLWNhbGxzDQogICAgZHBseXI6Om11dGF0ZSgNCiAgICAgIGAxYCA9IHJlcGxhY2VfbmEoYDFgLCAwKSwNCiAgICAgICMgYWNjb3VudCBmb3IgdW5vYnNlcnZlZCBhbGxlbGVzIG9uIGEgZ2l2ZW4gZGF5DQogICAgICBgMmAgPSByZXBsYWNlX25hKGAyYCwgMCkNCiAgICApICU+JQ0KICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoDQogICAgICBjb2xzID0gYDFgOmAyYCwNCiAgICAgIG5hbWVzX3RvID0gImFsbGVsZSIsDQogICAgICB2YWx1ZXNfdG8gPSAibiINCiAgICApICU+JQ0KICAgIGRwbHlyOjpncm91cF9ieShzdHJlYW0sIHllYXIsIERPWSwgbG9jdXMpICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoDQogICAgICBuX2FsbGVsZXMgPSBzdW0obiksDQogICAgICBwX2FsbGVsZXMgPSBuIC8gbl9hbGxlbGVzLA0KICAgICAgU05QID0gcGFzdGUobG9jdXMsIGFsbGVsZSwgc2VwID0gIl8iKQ0KICAgICkgJT4lIA0KICAgIGRwbHlyOjp1bmdyb3VwKCkNCikNCmBgYA0KDQojIyBWaWV3IEFsbGVsZSBGcmVxdWVuY2llcw0KDQpgYGB7cn0NCiMgIFRPRE8NCiMgZG91YmxlIGNoZWNrIGFsbGVsZSBmcmVxdWVuY3kgY29kZSB0byBtYWtlIHN1cmUgaXQgaXMgY29ycmVjdA0KIyBtYWtlIHN1cmUgYWxsZWxlcyBhcmUgYmVpbmcgYXNzaWduZWQgMSB2cy4gMiBjb25zaXN0ZW50bHkNCiMgZmlsdGVyIG91dCBoYXRjaGVyeSBzdHJheXM/DQojIGNoZWNrIHNhbXBsZSBzaXplcyBieSBzdHJlYW0veWVhci9ET1k/DQojIHNlZSBpZiBzcGF3bmluZyBzdGF0ZSBjb3JyZWxhdGVzPw0KYGBgDQoNCiMjIyBBZG1pcmFsdHkNCg0KYGBge3IgZmlnLmhlaWdodD0xMH0NCmNodW1fYWRhcHRpdmVfYWxsZWxlX2ZyZXEgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKHN0cmVhbSA9PSAiQWRtaXJhbHR5IikgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGxvY3VzID0gc3RyaW5ncjo6c3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gbG9jdXMsIHBhdHRlcm4gPSAiT2tlVjJfIikpICU+JSANCiAgZ2dwbG90Mjo6Z2dwbG90KGdncGxvdDI6OmFlcyh4ID0gRE9ZLCB5ID0gcF9hbGxlbGVzLCBjb2xvdXIgPSBTTlAsIGdyb3VwX2J5KFNOUCkpKSArDQogIGdncGxvdDI6Omdlb21fcG9pbnQoKSArDQogIGdncGxvdDI6Omdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHdlaWdodCA9ICJuX2FsbGVsZXMiLCBmb3JtdWxhID0geX54KSArDQogIGdncGxvdDI6OmZhY2V0X2dyaWQobG9jdXMgfiB5ZWFyKSArIA0KICBnZ3Bsb3QyOjp0aGVtZV9idygpICsgDQogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykNCmBgYA0KDQojIyMgRmlzaA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEwfQ0KY2h1bV9hZGFwdGl2ZV9hbGxlbGVfZnJlcSAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoc3RyZWFtID09ICJGaXNoIC0gRG91Z2xhcyBJc2xhbmQiKSAlPiUgDQogIGRwbHlyOjptdXRhdGUobG9jdXMgPSBzdHJpbmdyOjpzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSBsb2N1cywgcGF0dGVybiA9ICJPa2VWMl8iKSkgJT4lIA0KICBnZ3Bsb3QyOjpnZ3Bsb3QoZ2dwbG90Mjo6YWVzKHggPSBET1ksIHkgPSBwX2FsbGVsZXMsIGNvbG91ciA9IFNOUCwgZ3JvdXBfYnkoU05QKSkpICsNCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludCgpICsNCiAgZ2dwbG90Mjo6Z2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgd2VpZ2h0ID0gIm5fYWxsZWxlcyIsIGZvcm11bGEgPSB5fngpICsNCiAgZ2dwbG90Mjo6ZmFjZXRfZ3JpZChsb2N1cyB+IHllYXIpICsgDQogIGdncGxvdDI6OnRoZW1lX2J3KCkgKyANCiAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQ0KYGBgDQoNCiMjIyBQcm9zcGVjdA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEwfQ0KY2h1bV9hZGFwdGl2ZV9hbGxlbGVfZnJlcSAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoc3RyZWFtID09ICJQcm9zcGVjdCIpICU+JSANCiAgZHBseXI6Om11dGF0ZShsb2N1cyA9IHN0cmluZ3I6OnN0cl9yZW1vdmVfYWxsKHN0cmluZyA9IGxvY3VzLCBwYXR0ZXJuID0gIk9rZVYyXyIpKSAlPiUgDQogIGdncGxvdDI6OmdncGxvdChnZ3Bsb3QyOjphZXMoeCA9IERPWSwgeSA9IHBfYWxsZWxlcywgY29sb3VyID0gU05QLCBncm91cF9ieShTTlApKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX3BvaW50KCkgKw0KICBnZ3Bsb3QyOjpnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCB3ZWlnaHQgPSAibl9hbGxlbGVzIiwgZm9ybXVsYSA9IHl+eCkgKw0KICBnZ3Bsb3QyOjpmYWNldF9ncmlkKGxvY3VzIH4geWVhcikgKyANCiAgZ2dwbG90Mjo6dGhlbWVfYncoKSArIA0KICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpDQpgYGANCg0KIyMjIFNhd21pbGwNCg0KYGBge3IgZmlnLmhlaWdodD0xMH0NCmNodW1fYWRhcHRpdmVfYWxsZWxlX2ZyZXEgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKHN0cmVhbSA9PSAiU2F3bWlsbCIpICU+JSANCiAgZHBseXI6Om11dGF0ZShsb2N1cyA9IHN0cmluZ3I6OnN0cl9yZW1vdmVfYWxsKHN0cmluZyA9IGxvY3VzLCBwYXR0ZXJuID0gIk9rZVYyXyIpKSAlPiUgDQogIGdncGxvdDI6OmdncGxvdChnZ3Bsb3QyOjphZXMoeCA9IERPWSwgeSA9IHBfYWxsZWxlcywgY29sb3VyID0gU05QLCBncm91cF9ieShTTlApKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX3BvaW50KCkgKw0KICBnZ3Bsb3QyOjpnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCB3ZWlnaHQgPSAibl9hbGxlbGVzIiwgZm9ybXVsYSA9IHl+eCkgKw0KICBnZ3Bsb3QyOjpmYWNldF9ncmlkKGxvY3VzIH4geWVhcikgKyANCiAgZ2dwbG90Mjo6dGhlbWVfYncoKSArIA0KICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpDQpgYGANCg0KZW5k